<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Go4Pro.org</title>
	<atom:link href="http://legacy.go4pro.org/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://legacy.go4pro.org</link>
	<description>Go for Programming, Go for Professionals, Be a Professional!</description>
	<lastBuildDate>Sat, 26 Jun 2010 07:19:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Go4Pro.org迁移成功</title>
		<link>http://legacy.go4pro.org/?p=160</link>
		<comments>http://legacy.go4pro.org/?p=160#comments</comments>
		<pubDate>Sat, 26 Jun 2010 07:19:23 +0000</pubDate>
		<dc:creator>TR@SOE</dc:creator>
				<category><![CDATA[(Default)]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=160</guid>
		<description><![CDATA[Go4Pro.org站点已经正式迁移，原来的文章可以通过http://legacy.go4pro.org继续访问。 新的Go4pro.org站点将运行于Linode VPS主机上，基于Python和TG2构造，由TR@SOE、ch.linghu、raptor共同创建。]]></description>
			<content:encoded><![CDATA[<p>Go4Pro.org站点已经正式迁移，原来的文章可以通过<a href="http://legacy.go4pro.org">http://legacy.go4pro.org</a>继续访问。</p>
<p>新的Go4pro.org站点将运行于Linode VPS主机上，基于Python和TG2构造，由TR@SOE、ch.linghu、raptor共同创建。</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=160</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>An Android application with map</title>
		<link>http://legacy.go4pro.org/?p=149</link>
		<comments>http://legacy.go4pro.org/?p=149#comments</comments>
		<pubDate>Sun, 04 Apr 2010 08:36:42 +0000</pubDate>
		<dc:creator>TR@SOE</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[key]]></category>
		<category><![CDATA[map]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=149</guid>
		<description><![CDATA[I had read a few tutorials on how to develop an Android appication with map display. Today I had actually built one. I would like to summarize the key points/steps in making this application work. 1. First of all, get an Android Map API Key from Google. This actually involves two steps. Firstly, you will [...]]]></description>
			<content:encoded><![CDATA[<p>I had read a few tutorials on how to develop an Android appication with map display. Today I had actually built one. I would like to summarize the key points/steps in making this application work.</p>
<p>1. First of all, get an Android Map API Key from Google.</p>
<p>This actually involves two steps.</p>
<p>Firstly, you will have to create a key store to sign your Android application. This is quite easy and straightforward.</p>
<p>Secondly, apply an Android Map Key from Google: <a href="http://code.google.com/intl/zh-CN/android/maps-api-signup.html">http://code.google.com/intl/zh-CN/android/maps-api-signup.html</a>. To make this work, you have to know where is your key store file is located (as created in the above step) and also have the JDK tool named <strong>keytool</strong>. From the command line, type:</p>
<pre>
   keytool -list -keystore the-path-to-your-key-store-file
</pre>
<p>It will prompt you to enter the password to the key store and will generate a MD5 finger print for this particular key store file. Copy this MD5 finger print to the above URI and Google will give you the Android Map API Key immediately. It is strongly suggested to save this Key information.</p>
<p>2. Create an Android application.</p>
<p><strong>Note:</strong> It must be created with target set to: "Google APIs". You should not set the application target to "Android x.x" or it will not be running properly.</p>
<p><strong>Note:</strong> The target of the AVD that runs the map application must also be set to "Google APIs".</p>
<p><a href="http://www.go4pro.org/wp-content/uploads/2010/04/Android_target.jpg"><img src="http://www.go4pro.org/wp-content/uploads/2010/04/zrtn_002p2b58b771_tn.jpg" style="WIDTH: 400px; HEIGHT: 287px" height="287" width="400"/></a></p>
<p>The coding of the application is actually quite simple. There are only two points to be highlighted:</p>
<p>1. The application must be granted ACCESS_FINE_LOCATION and ACCESS_INTERNET permissions;</p>
<p>2. The mapview controll used in the view must provided with the API key generated in Step 1. It will look something like this:</p>
<pre lang="xml">
<com.google.android.maps.MapView
   android:id="@+id/myMapView"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:enabled="true"
   android:clickable="true"
   android:apiKey="your api key here"
/>
</pre>
<p>With these settings, the map application can eventually run successfully. However, in my implementation, the map shown in my AVD is only grids, no actuall maps at all. But in real machine (mine is Nexus One), the application is running correctly.</p>
</pre>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=149</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CodeIgniter和Flex的结合</title>
		<link>http://legacy.go4pro.org/?p=144</link>
		<comments>http://legacy.go4pro.org/?p=144#comments</comments>
		<pubDate>Fri, 26 Mar 2010 04:52:49 +0000</pubDate>
		<dc:creator>TR@SOE</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[ci]]></category>
		<category><![CDATA[code igniter]]></category>
		<category><![CDATA[flash]]></category>
		<category><![CDATA[flex]]></category>
		<category><![CDATA[XML]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=144</guid>
		<description><![CDATA[BT话痨群的后台中有一个彩蛋，只有资深BT才知道。 这个彩蛋是肾上开发的，目的是测试CI和Flex（Flash）的结合。 在这个彩蛋中，解决了一下几个问题： 用CI生成Flex需要的数据（XML格式）； 用Flex的HTTPService来获得数据并呈现； 用CI来包含生成的SWF文件并最终以Nice URI呈现。 一、用CI生成Flex需要的数据（XML格式） 请注意，我这里用了CI中控制器（Controller）来实现数据的获得，而且复用了models里的方法。这样做的好处是，在Flex中可以用一个URI来获取数据。 数据的输出是用load-&#62;view实现的，而在那个view文件中，我只是简单的按照XML格式输出数据而已： $nl="\n"; echo '&#60;?xml version="1.0" encoding="utf-8"?&#62;'.$nl; echo '&#60;catalog&#62;'.$nl; foreach($qd as $i) { $nc=$i-&#62;nc; $dw=$i-&#62;dw; echo "&#60;notice dw=\"$dw\"&#62;".$nl; echo "&#60;count&#62;$nc&#60;/count&#62;".$nl; echo "&#60;/notice&#62;".$nl; } echo '&#60;/catalog&#62;'; 二、用Flex的HTTPService来获得数据并呈现 Flex端的编写相对简单。关键代码如下： // 0) fix(e,"cb");} if (e.className=="di"){if (e.children(0).innerText.indexOf("\n")>0) fix(e,"db");} e.id=""; } function fix(e,cl){ e.className=cl; e.style.display="block"; j=e.parentElement.children(0); j.className="c"; k=j.children(0); k.style.visibility="visible"; k.href="#"; } function [...]]]></description>
			<content:encoded><![CDATA[<p>BT话痨群的后台中有一个彩蛋，只有资深BT才知道。</p>
<p>这个彩蛋是肾上开发的，目的是测试CI和Flex（Flash）的结合。</p>
<p><span id="more-144"></span></p>
<p>在这个彩蛋中，解决了一下几个问题：</p>
<ul>
<li>用CI生成Flex需要的数据（XML格式）；</li>
<li>用Flex的HTTPService来获得数据并呈现；</li>
<li>用CI来包含生成的SWF文件并最终以Nice URI呈现。</li>
</ul>
<p><strong>一、用CI生成Flex需要的数据（XML格式）</strong><br />
请注意，我这里用了CI中控制器（Controller）来实现数据的获得，而且复用了models里的方法。这样做的好处是，在Flex中可以用一个URI来获取数据。</p>
<p>数据的输出是用load-&gt;view实现的，而在那个view文件中，我只是简单的按照XML格式输出数据而已：</p>
<pre lang="php">$nl="\n";
echo '&lt;?xml version="1.0" encoding="utf-8"?&gt;'.$nl;

echo '&lt;catalog&gt;'.$nl;

foreach($qd as $i)
{
    $nc=$i-&gt;nc;
    $dw=$i-&gt;dw;

    echo "&lt;notice dw=\"$dw\"&gt;".$nl;
    echo "&lt;count&gt;$nc&lt;/count&gt;".$nl;

    echo "&lt;/notice&gt;".$nl;
}

echo '&lt;/catalog&gt;';
</pre>
<p><strong>二、用Flex的HTTPService来获得数据并呈现</strong></p>
<p>Flex端的编写相对简单。关键代码如下：</p>
<p><!-- BODY{font:x-small 'Verdana';margin-right:1.5em} .c{cursor:hand} .b{color:red;font-family:'Courier New';font-weight:bold;text-decoration:none} .e{margin-left:1em;text-indent:-1em;margin-right:1em} .k{margin-left:1em;text-indent:-1em;margin-right:1em} .t{color:#990000} .xt{color:#990099} .ns{color:red} .dt{color:green} .m{color:blue} .tx{font-weight:bold} .db{text-indent:0px;margin-left:1em;margin-top:0px;margin-bottom:0px;padding-left:.3em;border-left:1px solid #CCCCCC;font:small Courier} .di{font:small Courier} .d{color:blue} .pi{color:blue} .cb{text-indent:0px;margin-left:1em;margin-top:0px;margin-bottom:0px;padding-left:.3em;font:small Courier;color:#888888} .ci{font:small Courier;color:#888888} PRE{margin:0px;display:inline} --><script type="text/javascript">// <![CDATA[
// <![CDATA[
function f(e){
if (e.className=="ci"){if (e.children(0).innerText.indexOf("\n")>0) fix(e,"cb");}
if (e.className=="di"){if (e.children(0).innerText.indexOf("\n")>0) fix(e,"db");}
e.id="";
}
function fix(e,cl){
e.className=cl;
e.style.display="block";
j=e.parentElement.children(0);
j.className="c";
k=j.children(0);
k.style.visibility="visible";
k.href="#";
}
function ch(e){
mark=e.children(0).children(0);
if (mark.innerText=="+"){
mark.innerText="-";
for (var i=1;i<e.children.length;i++)
e.children(i).style.display="block";
}
else if (mark.innerText=="-"){
mark.innerText="+";
for (var i=1;i<e.children.length;i++)
e.children(i).style.display="none";
}}
function ch2(e){
mark=e.children(0).children(0);
contents=e.children(1);
if (mark.innerText=="+"){
mark.innerText="-";
if (contents.className=="db"||contents.className=="cb")
contents.style.display="block";
else contents.style.display="inline";
}
else if (mark.innerText=="-"){
mark.innerText="+";
contents.style.display="none";
}}
function cl(){
e=window.event.srcElement;
if (e.className!="c"){e=e.parentElement;if (e.className!="c"){return;}}
e=e.parentElement;
if (e.className=="e") ch(e);
if (e.className=="k") ch2(e);
}
function ex(){}
function h(){window.status=" ";}
document.onclick=cl;
// ]]&gt;</script></p>
<div>&lt;?xml version="1.0" encoding="utf-8"  ?&gt;</div>
<div>
<div><a onclick="return false" href="#" onfocus="h()">-</a> &lt;mx:Application xmlns:mx="<strong>http://www.adobe.com/2006/mxml</strong>" layout="<strong>absolute</strong>"  initialize="<strong>weekday.send()</strong>"&gt;</div>
<div>
<div>
<div>&lt;mx:HTTPService id="<strong>weekday</strong>" url="<strong>http://www.bspmq.com/status/index.php/welcome/xml_weekday</strong>" /&gt;</div>
</div>
<div>
<div><a onclick="return false" href="#" onfocus="h()">-</a> &lt;mx:Panel title="<strong>BSpMq.com站点统计</strong>"  layout="<strong>vertical</strong>" color="<strong>0xffffff</strong>" width="<strong>600</strong>"  height="<strong>300</strong>"&gt;</div>
<div>
<div>
<div><a onclick="return false" href="#" onfocus="h()">-</a> &lt;mx:AreaChart id="<strong>chartStatus</strong>"  color="<strong>0x323232</strong>" height="<strong>100%</strong>" showDataTips="<strong>true</strong>"  dataProvider="<strong>{weekday.lastResult.catalog.notice}</strong>"&gt;</div>
<div>
<div>
<div><a onclick="return false" href="#" onfocus="h()">-</a> &lt;mx:horizontalAxis&gt;</div>
<div>
<div>
<div>&lt;mx:CategoryAxis categoryField="<strong>dw</strong>" /&gt;</div>
</div>
<div>&lt;/mx:horizontalAxis&gt;</div>
</div>
</div>
<div>
<div><a onclick="return false" href="#" onfocus="h()">-</a> &lt;mx:series&gt;</div>
<div>
<div>
<div>&lt;mx:AreaSeries yField="<strong>count</strong>" form="<strong>segment</strong>"  displayName="<strong>话痨数量</strong>" /&gt;</div>
</div>
<div>&lt;/mx:series&gt;</div>
</div>
</div>
<div>&lt;/mx:AreaChart&gt;</div>
</div>
</div>
<div>
<div>&lt;mx:Button label="<strong>Get Counts!</strong>" click="<strong>weekday.send()</strong>"  /&gt;</div>
</div>
<div>&lt;/mx:Panel&gt;</div>
</div>
</div>
<div>&lt;/mx:Application&gt;</div>
</div>
</div>
<p>关键是使用HTTPService部件来获得数据。它的URL属性由于在第一步的准备工作，可以是一个很漂亮的URI。</p>
<p><strong>三、用CI来包含生成的SWF文件并最终以Nice URI呈现。</strong></p>
<p>再次回到PHP中，在controller中我创建了一个action，来调用对应的嵌入了SWF的模板：</p>
<pre lang="html">&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100%" height="100%" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;
&lt;param name="name" value="main" /&gt;&lt;param name="bgcolor" value="#869ca7" /&gt;
&lt;param name="align" value="middle" /&gt;
&lt;param name="src" value="http://www.?????.com/status/system/application/views/bspmq.swf" /&gt;
&lt;param name="quality" value="high" /&gt;
&lt;embed type="application/x-shockwave-flash" width="100%" height="100%" src="http://www.?????.com/status/system/application/views/bspmq.swf" quality="high" align="middle" bgcolor="#869ca7" name="main"&gt;
&lt;/embed&gt;
&lt;/object&gt;
</pre>
<p>这个文件基本上可以由Flex自己生成，只要稍作修改以符合要求即可。</p>
<p>在BT群话痨系统中，你可以用http://xxxxxxxxxxxx/status/index.php/welcome/swf来看到效果：</p>
<p><a href="http://www.rsywx.net/wordpress/wp-content/uploads/2010/03/stat_swf.jpg"><img class="alignnone size-medium wp-image-2179" title="stat_swf" src="http://www.rsywx.net/wordpress/wp-content/uploads/2010/03/stat_swf-300x247.jpg" alt="" width="300" height="247" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=144</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TurboGears 2.0 使用 routes</title>
		<link>http://legacy.go4pro.org/?p=130</link>
		<comments>http://legacy.go4pro.org/?p=130#comments</comments>
		<pubDate>Mon, 12 Oct 2009 13:12:37 +0000</pubDate>
		<dc:creator>令狐虫</dc:creator>
				<category><![CDATA[Turbo Gear]]></category>
		<category><![CDATA[pylons]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[turbogears 2.0]]></category>
		<category><![CDATA[web framework]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=130</guid>
		<description><![CDATA[狗屎皮的设计上碰到了一个URL的问题。根据需求，/article/[id]显示指定ID的文章，而/article/recent显示最新文章列表。从URL设计来说，我个人感觉问题也不大，逻辑还是挺清楚的。但是因为TG2用的是原来CherryPy风格的url mapping，也就是用@expose的方法名直接对应url，要实现这种功能就不太方便了。当然你也可以这样写： def article(self, id): if id=='recent': return self.article_recent() else: return self.article_id(id) 不过，这个方法未免也太ugly了。而且也不利于扩展和做url导航。事实上，pylons自带的routes倒是可以很方便的实现这个需求。而TG2是基于Pylons的。更何况TG2的文档都说了，只要重定义setup_routes函数就可以。因此我开始并没有把这个当回事。直接开干吧。 这里要说一句题外话——当然可能是废话——在前面提到的文档里，并没有清楚的表明如何“重定义”setup_routes函数。事实上，在它提到的 app_cfg.py中并没有setup_routes的定义。当然，我们通过app_cfg.py中对AppConfig类的实例化为线索，可以在 lib/site-packages/TurboGears-xx.xxxx.egg/tg/configuration.py里找到AppConfig 类，其中有这个setup_routes函数的定义。但是，显然我们不能直接改这里。比较正统的做法是从AppConfig类派生一个新的类，重载 setup_routes方法： class MyAppConfig(AppConfig): def setup_routes(self): .... #实例化MyAppConfig而不是原来的AppConfig base_config = MyAppConfig() .... 不过在一个动态语言中，重载一个类的方法，不需要这样大动干戈，我们只要将AppConfig的对应方法替换掉就可以了。 # We override the setup_routes function so that we abandon the TG-style routing # and recovery to the pylons-style routing map. def setup_routes(self): """Setup the default [...]]]></description>
			<content:encoded><![CDATA[<p>狗屎皮的设计上碰到了一个URL的问题。根据需求，/article/[id]显示指定ID的文章，而/article/recent显示最新文章列表。从URL设计来说，我个人感觉问题也不大，逻辑还是挺清楚的。但是因为TG2用的是原来CherryPy风格的url mapping，也就是用@expose的方法名直接对应url，要实现这种功能就不太方便了。当然你也可以这样写：</p>
<pre lang="python">def article(self, id):
    if id=='recent':
         return self.article_recent()
   else:
        return self.article_id(id)</pre>
<p><span id="more-130"></span><br />
不过，这个方法未免也太ugly了。而且也不利于扩展和做url导航。事实上，pylons自带的routes倒是可以很方便的实现这个需求。而TG2是基于Pylons的。更何况TG2的文档都说了，只要重定义setup_routes函数就可以。因此我开始并没有把这个当回事。直接开干吧。</p>
<p>这里要说一句题外话——当然可能是废话——在前面提到的文档里，并没有清楚的表明如何“重定义”setup_routes函数。事实上，在它提到的 app_cfg.py中并没有setup_routes的定义。当然，我们通过app_cfg.py中对AppConfig类的实例化为线索，可以在 lib/site-packages/TurboGears-xx.xxxx.egg/tg/configuration.py里找到AppConfig 类，其中有这个setup_routes函数的定义。但是，显然我们不能直接改这里。比较正统的做法是从AppConfig类派生一个新的类，重载 setup_routes方法：</p>
<pre lang="python">class MyAppConfig(AppConfig):
    def setup_routes(self):
        ....

#实例化MyAppConfig而不是原来的AppConfig
base_config = MyAppConfig()
....</pre>
<p>不过在一个动态语言中，重载一个类的方法，不需要这样大动干戈，我们只要将AppConfig的对应方法替换掉就可以了。</p>
<pre lang="python">
# We override the setup_routes function so that we abandon the TG-style routing
# and recovery to the pylons-style routing map.
def setup_routes(self):
    """Setup the default TG2 routes
    Override this and set up your own routes maps if you want to use routes.
    """
    map = Mapper(directory=config['pylons.paths']['controllers'],
                always_scan=config['debug'])

    # Setup custem route for dispatch
    map.connect('recent', '/article/recent', controller='root', action='recent_artcles')
    map.connect('article', '/article/{id}', controller='root', action='article')

    # Setup a default route for the root of object dispatch
    map.connect('*url', controller='root', action='routes_placeholder')

    config['routes.map'] = map

# 替换AppConfig的setup_routes方法
AppConfig.setup_routes = setup_routes
base_config = AppConfig()
</pre>
<p>替换之后，新的setup_routes就会工作了。于是自信满满的启动服务，打开浏览器……看到一片出错信息…… 囧。</p>
<p>之后的艰苦调试工作就不多说了，总之研究到最后，我发现，这应该是TurboGears 2.0的一个bug。为什么这么说呢？</p>
<p>我最初一直以为，map.connect('*url', controllers='root', action='routes_placeholder') 一句的作用是将所有的url交由routes_placeholder函数进行处理，这应该是一个通用的url mapping函数。但是实际跟踪代码发现，这个函数实际上什么也没做，只是简单的pass而已（至此我才真正理解它为什么叫做placeholder而不是handler）。</p>
<p>那么实际的url mapping工作在哪儿做呢？</p>
<p>跟踪pylons代码我们可以注意到，PylonsApp将Routes的解析结果存入environ['pylons.routes_dict']，然后在WSGICotroller中根据routes_dict来分派执行action。最终的工作是在_perform_call这个函数中做的。在WSGIController中这个函数的实现非常简单，因为之前已经做了大量的工作解析了需要执行的函数和参数，在这里只是简单调用了func(**args)并返回结果而已。</p>
<p>TG2 的DecoratedController继承了Pylons的WSGIController，继而进一步派生出TGController(这个继承关系在TG 2.1中被修改，不过不会影响我们下面的讨论)，在DecoratedController中，重载了_perform_call，在其中假设需要调用的函数都经过了扩展，变成了一个具有decoration属性的controller。而在TGController中，再次重载 _perform_call，通过在里面调用_get_routing_info函数让结果变成经过扩展的具有decoration属性的 controller，以符合DecoratedController的处理要求。</p>
<p>问题就坏在那个_get_routing_info函数上。这个函数重新实现了自己的url mapping，而丝毫没有顾及到原先的pylons.routes_dict，因此原先用RoutesMiddleware处理而得的结果在这里完全被放弃，这也就是为什么自定义routes失效的原因。当然我相信这是一个bug，因为TG2的文档还认为自己可以正确的处理自定义routes，因此我觉得TG2原先的需求并没有完全替换routes的意思。只是实际的实现出了问题。</p>
<p>好在网上有人已经给出了<a href="http://simplestation.com/locomotion/routes-in-turbogears2/">一个workaround的解决方案</a>：实现一个类似于TGController的Controller，模仿TGController的_perform_call处理，但是不使用 _get_routing_info，这样就既能保留TG2的那些Controller扩展功能（比如很好用很清晰的@expose指定模板之类）又可以得到自定义routes的功能。然而不幸的是，他给出的代码是不能执行的！</p>
<p>幸好问题不算严重，我进行了一点修改之后，可以用于我们目前使用的TG2.0.3版本了。据说TG2.1修改了TGController的类继承结构，有可能这个代码需要进一步修改，不过在2.1正式放出之前，我们暂时不操这份心了。</p>
<p>修改后的代码如下，除了修正原代码的bug之外，我还做了一点点的增强，以最大限度的接近pylons的routes使用感受。</p>
<pre lang="python">
# Routes function cannot be used because of a routing bug of TurboGear2
# The modified RoutingController can work around it.
# Original version was created by Anthony Theocharis
# (http://simplestation.com/locomotion/routes-in-turbogears2/)
# but it cannot run so it's modified by ch.linghu 

class RoutingController(TGController):
    def _perform_call(self, func, args):
        if not args:
            args = {}

        try:
            # If these are the __before__ or __after__ methods, they will have no decoration property
            # This will make the default DecoratedController._perform_call() method choke
            # We'll handle them just like TGController handles them.
            func_name = func.__name__
            if func_name == '__before__' or func_name == '__after__':
                aname = str(args.get('action', 'lookup'))
                if hasattr(self, aname):
                    controller = getattr(self, aname)
                else:
                    if asbool(config['debug']):
                        raise NotImplementedError("Action '%s' not implemented" % aname)
                    else:
                        raise HTTPNotFound().exception

                if func_name == '__before__' and hasattr(controller.im_class, '__before__'):
                    return controller.im_self.__before__(*args)
                if func_name == '__after__' and hasattr(controller.im_class, '__after__'):
                    return controller.im_self.__after__(*args)
                return

            else:
                controller = func
                params = args
                remainder = ''

                result = DecoratedController._perform_call(
                    self, controller, params, remainder=remainder)

        except HTTPException, httpe:
            result = httpe
            # 304 Not Modified's shouldn't have a content-type set
            if result.status_int == 304:
                result.headers.pop('Content-Type', None)
            result._exception = True

        return result
</pre>
<p>然后再将lib/base.py中的BaseController从TGController继承改成由RoutingController继承。基本的自定义routes工作就完成了。</p>
<p>但是这样一来，原先的自定义错误处理又失效了，我们需要从pylons项目中借鉴一点代码还恢复它：</p>
<p>首先在routes中定义error url：</p>
<pre lang="python">
def setup_routes(self):
    """Setup the default TG2 routes
    Override this and set up your own routes maps if you want to use routes.
    """
    map = Mapper(directory=config['pylons.paths']['controllers'],
                always_scan=config['debug'])
    # Custom Error Handler
    map.connect('/error/{action}/{id}', controller='error')
    # Setup custem route for dispatch
    # NOTE: The meaning of connect's 4 arguments:
    #      name(can be used by url_for function), url, controller, action
    map.connect('home', '/', controller='root', action='index')

    #注意：这两行前后顺序不能更换！
    map.connect('recent', '/article/recent', controller='root', action='recent_artcles')
    map.connect('article', '/article/{id}', controller='root', action='article')

    map.connect('archive', '/archive/{year}/{month}', controller='root', action='archive')
    map.connect('catelog', '/catelog/{catelog_name}', controller='root', action='catelog')
    map.connect('tag', '/tag/{tag_name}', controller='root', action='tag')

    # Setup a default route for the root of object dispatch
    ########################################################
    #我们修改BaseController的父类之后这个处理实际上已经失效了，因此不用再留着
    #我们完全使用routes style进行URL处理
    ########################################################
    #map.connect('*url', controller='root', action='routes_placeholder')

    config['routes.map'] = map
</pre>
<p>然后，我们要把controller中的error.py从一个普通的类（继承自object）改成一个真正的WSGIController，方法很简单，让它继承自BaseController就行了，这个我就不给代码了。</p>
<p>最后，套上Pylons中自定义错误信息的middleware，在middleware.py的return app之前加上一段：</p>
<pre lang="python">
    # Wrap your base TurboGears 2 application with custom middleware here
    # Add the custom error handler because of we use the pylons-style routing
    if asbool(config['debug']):
        app = StatusCodeRedirect(app)
    else:
        app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
</pre>
<p>并加上必需的import：</p>
<pre lang="python">
from pylons.middleware import StatusCodeRedirect
from tg.configuration import config
from paste.deploy.converters import asbool
</pre>
<p>大功告成！</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=130</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用unittest测试web2py应用中的非页面部分</title>
		<link>http://legacy.go4pro.org/?p=128</link>
		<comments>http://legacy.go4pro.org/?p=128#comments</comments>
		<pubDate>Sun, 27 Sep 2009 15:57:46 +0000</pubDate>
		<dc:creator>猛禽</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[unitest]]></category>
		<category><![CDATA[web2py]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=128</guid>
		<description><![CDATA[虽然web2py提供了用doctest进行测试的方式，但是这个只能在controller层直接对页面交互进行测试。虽然这样的测试覆盖很全面，但是测试粒度太大，对我来说测得不够细。 我 的设计习惯是controller层只进行简单的调用转换，实际的业务逻辑放在module层（注意：不是model层）处理，虽然在model里进行设 置后借助于Generic View也能干很多事情，但是一方面我不习惯这种方式，另一方面这种方式还是有一定的局限性。 既然业务逻辑在 module中，那么我就需要在不借助页面的情况下对这些module进行单独的测试，这是doctest所做不到的——我又不想为了测试而特地弄几个页 面。于是研究了一下如何用unittest对module进行测试——问题在关键在于如何在unittest中创建web2py的运行环境。而这一环境的 核心就是db——如何初始化一个db供被测试的module使用。 其实要做到这一点并不难。gluon.shell包中提供了一个函数exec_environment来实现这一功能。假设有一个db.py是这样的： db.define_table('tabletest',     SQLField('code'),     SQLField('name')) 那么测试用例可以写成这样： import sys import os web2py_path = '../../..' sys.path.append(os.path.realpath(web2py_path)) sys.path.append(os.path.realpath('..')) os.chdir(web2py_path) import unittest from gluon.shell import exec_environment from modules import datamodule class TestDefaultController(unittest.TestCase): def setUp(self): self.dbm=exec_environment('applications/yourapp/models/db.py') def tearDown(self): db=self.dbm.db query=(db.tabletest.code=="000111") db(query).delete() def testDB(self): db=self.dbm.db x=db.tabletest.insert(code="000111", name="test name") query=(db.tabletest.id==x) y=db(query).select()[0].id self.assertEqual(x,y) [...]]]></description>
			<content:encoded><![CDATA[<p>虽然web2py提供了用doctest进行测试的方式，但是这个只能在controller层直接对页面交互进行测试。虽然这样的测试覆盖很全面，但是测试粒度太大，对我来说测得不够细。<span id="more-128"></span></p>
<p>我 的设计习惯是controller层只进行简单的调用转换，实际的业务逻辑放在module层（注意：不是model层）处理，虽然在model里进行设 置后借助于Generic View也能干很多事情，但是一方面我不习惯这种方式，另一方面这种方式还是有一定的局限性。</p>
<p>既然业务逻辑在 module中，那么我就需要在不借助页面的情况下对这些module进行单独的测试，这是doctest所做不到的——我又不想为了测试而特地弄几个页 面。于是研究了一下如何用unittest对module进行测试——问题在关键在于如何在unittest中创建web2py的运行环境。而这一环境的 核心就是db——如何初始化一个db供被测试的module使用。</p>
<p>其实要做到这一点并不难。gluon.shell包中提供了一个函数exec_environment来实现这一功能。假设有一个db.py是这样的：</p>
<pre>db.define_table('tabletest',
    SQLField('code'),
    SQLField('name'))</pre>
<p>那么测试用例可以写成这样：</p>
<pre>import sys
import os
web2py_path = '../../..'
sys.path.append(os.path.realpath(web2py_path))
sys.path.append(os.path.realpath('..'))
os.chdir(web2py_path)

import unittest
from gluon.shell import exec_environment
from modules import datamodule

class TestDefaultController(unittest.TestCase):
    def setUp(self):
        self.dbm=exec_environment('applications/yourapp/models/db.py')

    def tearDown(self):
        db=self.dbm.db
        query=(db.tabletest.code=="000111")
        db(query).delete()

    def testDB(self):
        db=self.dbm.db
        x=db.tabletest.insert(code="000111", name="test name")
        query=(db.tabletest.id==x)
        y=db(query).select()[0].id
        self.assertEqual(x,y)

    def testDataModule(self):
        db=self.dbm.db
        x=datamodule.foo(db, ...)
        self.assert...

if __name__ == '__main__':
    unittest.main()</pre>
<p>代码很简单，最开始一段是准备程序的运行路径，因为这个测试用例的文件是放在： web2py/applications/yourapp/tests下的，所以用"../../.."来指向web2py的根目录，并定位当前位置于此。</p>
<p>重 点在于setUp函数中调用了exec_environment函数来执行db.py，这样就能够为测试准备好web2py的运行环境，并启用db.py 中的model定义。之后在testDB和testDataModule中就可以使用db进行数据库访问并检测执行结果。这与一般的unittest应用 没什么不同。</p>
<p>最后需要注意的一点是：不能用windows exe版的web2py，要用源码版的web2py才能正常使用unittest，否则会出错。</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=128</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>xpath在HTML解析中的应用（更新加强版）</title>
		<link>http://legacy.go4pro.org/?p=118</link>
		<comments>http://legacy.go4pro.org/?p=118#comments</comments>
		<pubDate>Tue, 01 Sep 2009 04:42:11 +0000</pubDate>
		<dc:creator>猛禽</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[libxml2]]></category>
		<category><![CDATA[lxml]]></category>
		<category><![CDATA[re]]></category>
		<category><![CDATA[xhtml]]></category>
		<category><![CDATA[xpath]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=118</guid>
		<description><![CDATA[对比三种方法应该可以看出xpath和jQuery对于页面的解析都是基于XML的语义进行，而RE则纯粹是基于plain text。RE对付简单的页面是没有问题，如果页面结构复杂度较高的时候（比如一堆的DIV来回嵌套之类），设计一个恰当的RE pattern可能会远比写一个xpath要复杂。特别是目前主流的基于CSS的页面设计方式，其中大部分关键节点都会有id——对于使用jQuery的页面来说则更是如此，这时xpath相比RE就有了决定性的优势。]]></description>
			<content:encoded><![CDATA[<p>前一阵参加了一个Python的活动，其间老董的讲座是讨论网页爬虫技术的。其中提到了一下关于页面解析的问题，他推荐了三种技术。其中有用到libxml2里的xpath来处理，我就跟令狐谈到我曾经也用过这个东东。令狐建议我把这个东东说一下，于是我就写了这一篇。<span id="more-118"></span></p>
<p>惭 愧的是我最初在python里用xpath时用的不是libxml2，而是一个不记得是什么的XML库。后来因为那个库不知道为什么找不到了或者是新版本 不再提供xpath支持等原因，才通过google找到libxml2。就像对pcre的误解一样，我原来还以为libxml2是python的库，后来 才知道它是C的库，我用的只是python的包装而已。</p>
<p>很多对XML接触不多的人，刚开始的印象都会觉得XML不就是一种数据存储方式嘛，但是实际上在一些极端XMLer看来，XML是一种强大的编程语言。不相信的话试着写一个XSL就知道了。我对xpath的了解其实也是源于多年写的一个程序需要而对XSLT作了点研究。</p>
<p>关于xpath的权威资料，当然要算W3C的<a href="http://www.w3.org/TR/xpath">XPath官方文档</a>。</p>
<p>下面以一个例子来说明吧。假设现在我们需要解析这样一个“<a href="http://borland.mblogger.cn/raptor/">页面</a>”，取出其中所有“档案”段的内容，并且解析为一个个月份值。这个功能当然也可以用正则表达式（RE）来式来实现——这也是老董提到的三种技术之一——而且事实上有些情况下使用RE并不会比用xpath麻烦。</p>
<p>首先，我们需要安装一个支持xpath的python库。目前在libxml2的网站上被推荐的python binding是lxml（我以前用的不是这个，不过我也不记得是哪个了），所以现在就以这个为例吧。</p>
<p>安装方法很简单： easy_install lxml 即可——什么？你不知道什么叫easy_install？那就猛戳<a href="http://peak.telecommunity.com/DevCenter/EasyInstall">PEAK这里</a>学习一下吧。</p>
<p>个么然后是不是就可以直接用了呢？试试看吧：</p>
<pre>import codecs
from lxml import etree

f=codecs.open("raptor.htm","r","utf-8")
tree=etree.parse(f)</pre>
<p>很不幸，可耻滴失败鸟。因为这个页面并不是一个qualified的XHTML——别说中国了，就算在外国也没有那么多合格的XHTML页面。</p>
<p>要解决这个问题，当然可以通过HTML tidy之类的来做一个预处理，或者用RE做一个预处理。不过还好经过一番研究，发现libxml2其实已经内置了解决方案——即使是不很规范的HTML也可以。</p>
<p>现在来看如何直接使用lxml处理：</p>
<pre>import codecs
from lxml import etree

f=codecs.open("raptor.htm","r","utf-8")
content=f.read()
f.close()
tree=etree.HTML(content)</pre>
<p>Bingo！果然成功。关键就在于etree提供了HTML这个解析函数。之后的事情就好办多了，因为可以直接对HTML使用xpath。</p>
<p>在使用xpath之前我们先来看看作为对照的jQuery和RE。</p>
<p>在jQuery里要处理这种东西就很简单，特别是假如那个ul节点有id的话（比如是&lt;ul id='archive'&gt;）：</p>
<pre>$("#archive").each(function(){...});</pre>
<p>就这个实际的例子来说则稍微复杂一些：</p>
<pre>$("#leftmenu").children("h3:contains('档案')").next("ul").each(function(){...});</pre>
<p>意思是：找到id为leftmenu的节点，在其下找到一个内容包含为"档案"的h3节点，再取其接下来的一个ul节点。</p>
<p>但是在python里要是用RE来处理就略麻烦一些，比如这样：</p>
<pre>block_pattern=re.compile(u"&lt;h3&gt;档案&lt;/h3&gt;(.*?)&lt;h3&gt;", re.I | re.S)
m=block_pattern.findall(content)
item_pattern=re.compile(u"&lt;li&gt;(.*?)&lt;/li&gt;", re.I | re.S)
items=item_pattern.findall(m[0])
for i in items:
    print i</pre>
<p>那么换成用xpath要怎么做呢？其实跟jQuery是差不多的，如果有id的话：</p>
<pre>nodes=tree.xpath("/descendant::ul[@id='archive']")</pre>
<p>当然，现在没有的话也就只能用类似于jQuery的方法了。完整的xpath应该是这样写的（注意，原文件中的TAG有大小写的情况，但是在XPATH里只能用小写）：</p>
<pre>nodes=tree.xpath(u"/html/body/form/div[@id='leftmenu']/h3[text()='档案']/following-sibling::ul[1]")</pre>
<p>这句xpath的意思是寻找这样一个东东。</p>
<pre>&lt;html&gt;
  &lt;body&gt;
    &lt;form&gt;
      &lt;div id='leftmenu'&gt;
        &lt;h3&gt;档案&lt;/h3&gt;
        &lt;ul&gt;<span style="font-weight: bold;">&lt;!-- 找到这里 --&gt;</span>&lt;/ul&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>
<p>当然更简单的方法就是像jQuery那样直接根据id定位：</p>
<pre>nodes=tree.xpath(u"//div[@id='leftmenu']/h3[text()='档案']/following-sibling::ul[1]")</pre>
<p>这两种方法返回的结果中，nodes[0]就是那个“档案”的h3节点后面紧跟的第一个ul节点。</p>
<p>之后就可以把每个月份的文本列出（注意，是以上面取得的ul节点为起点）：</p>
<pre>nodes=nodes[0].xpath("li/a")
for n in nodes:
    print n.text</pre>
<p>这段的意思是取得这个ul节点下的所有&lt;li&gt;&lt;a&gt;节点。之后的循环就是把这些节点的文本内容列出。</p>
<p>对 比三种方法应该可以看出xpath和jQuery对于页面的解析都是基于XML的语义进行，而RE则纯粹是基于plain text。RE对付简单的页面是没有问题，如果页面结构复杂度较高的时候（比如一堆的DIV来回嵌套之类），设计一个恰当的RE pattern可能会远比写一个xpath要复杂。特别是目前主流的基于CSS的页面设计方式，其中大部分关键节点都会有id——对于使用jQuery的 页面来说则更是如此，这时xpath相比RE就有了决定性的优势。</p>
<p>libxml2实在是太强大了。</p>
<p style="font-weight: bold;">附录：XPATH的简单语法介绍</p>
<p>XPATH基本上是用一种类似目录树的方法来描述在XML文档中的路径。比如用“/”来作为上下层级间的分隔。第一个“/”表示文档的根节点（注意，不是指文档最外层的tag节点，而是指文档本身）。比如对于一个HTML文件来说，最外层的节点应该是"/html"。</p>
<p>同样的，“..”和“.”分别被用来表示父节点和本节点。</p>
<p>XPATH返回的不一定就是唯一的节点，而是符合条件的所有节点。比如在HTML文档里使用“/html/head/scrpt”就会把head里的所有script节点都取出来。</p>
<p>为了缩小定位范围，往往还需要增加过滤条件。过滤的方法就是用“[”“]”把过滤条件加上。比如在HTML文档里使用“/html/body/div[@id='main']”，即可取出body里id为main的div节点。</p>
<p>其中@id表示属性id，类似的还可以使用如@name, @value, @href, @src, @class....</p>
<p>而 函数text()的意思则是取得节点包含的文本。比如：&lt;div&gt;hello&lt;p&gt;world&lt;/p&gt;&lt; /div&gt;中，用"div[text()='hello']"即可取得这个div，而world则是p的text()。</p>
<p>函数position()的意思是取得节点的位置。比如“li[position()=2]”表示取得第二个li节点，它也可以被省略为“li[2]”。</p>
<p>不过要注意的是数字定位和过滤 条件的顺序。比如“ul/li[5][@name='hello']”表示取ul下第五项li，并且其name必须是hello，否则返回空。而如果用 “ul/li[@name='hello'][5]”的意思就不同，它表示寻找ul下第五个name为"hello“的li节点。</p>
<p>此外，“*”可以代替所有的节点名，比如用"/html/body/*/span"可以取出body下第二级的所有span，而不管它上一级是div还是p或是其它什么东东。</p>
<p>而 “descendant::”前缀可以指代任意多层的中间节点，它也可以被省略成一个“/”。比如在整个HTML文档中查找id为“leftmenu”的 div，可以用“/descendant::div[@id='leftmenu']”，也可以简单地使用“ //div[@id='leftmenu']”。</p>
<p>至于“following-sibling::”前缀就如其名所说，表示同一层的下一个节点。"following-sibling::*"就是任意下一个节点，而“following-sibling::ul”就是下一个ul节点。</p>
<p>更复杂的XPATH语法还是请参考官文档《<a href="http://www.w3.org/TR/xpath">XML Path Language (XPath)</a>》。</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=118</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Cygwin 1.7版 中文问题的解决（99%）</title>
		<link>http://legacy.go4pro.org/?p=116</link>
		<comments>http://legacy.go4pro.org/?p=116#comments</comments>
		<pubDate>Tue, 04 Aug 2009 06:56:45 +0000</pubDate>
		<dc:creator>令狐虫</dc:creator>
				<category><![CDATA[System]]></category>
		<category><![CDATA[cygwin]]></category>
		<category><![CDATA[encoding]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[中文]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=116</guid>
		<description><![CDATA[Linux玩久了之后，在Windows下用不了很多优秀的命令行工具，就会感觉特别不爽。因此我一般都会在电脑上安装一套GNU utils for windows。最开始的时候，我用的是minGW里的utils，但是它附带的工具不全，虽然有一部分其他工具可以在其他地方google到，毕竟比较费神费力。 后来好好玩了一下Cygwin，开始喜欢上这个玩意儿了。以前一直以为cygwin就是开始菜单里启动的那个bash界面，后来发现其实不是，cygwin实际上是通过一个cygwin1.dll实现了几乎全部的UNIX函数，因此只要链接到这个dll，就可以很方便的port各种UNIX工具了。port出来的工具，也可以在DOS命令行下正常执行。 因为Cygwin的安装是集中式的，有点类似于apt机制，这很方便，不需要到处找各种各样的port了，只要从registry里安装就行。 于是我将绝大部分的命令行工具都通过cygwin安装使用，然后在PATH里填上cygwin/bin的路径就行了，非常方便。包括原来在Windows下单独安装的python、hg等工具，都换成了cygwin版本的。 但是最近在做一个工作上用的数据导入工具的时候，麻烦来了。 因为导入文件有可能是中文文件名，或者存在于中文路径中，我发现一旦涉及中文，cygwin版的python输出就是乱码，后来进一步发现，python里直接输出的中文也是有问题的。 然后，我联想到使用cygwin命令的时候，对中文的处理也是不尽人意的，比如ls如果碰到中文文件名，就会输出成一堆问号。 这次我想干脆将这些问题一起搞定。 于是就开始了找资料之旅。 网上很多处理cygwin中文的资料，都是针对bash的，然而我对cygwin的使用，在绝大部分情况下都不涉及bash。因此肯定要寻求其他途径。 然后又找到一个日本人改造的cygwin的utf8版。这个倒是有用，不过他做的是1.5版的cygwin，而我现在使用的是1.7beta版的。降级，这个事情我可有点不甘愿。先放着吧，实在不行再采取这个方案。 google了一大圈无果，今天突然想起来去cygwin的官方网站瞧瞧。没想到这一瞧还真有收获，在1.7版的new feature中，赫然写着 To always have a valid string, use the UTF-8 charset by setting the environment variable $LANG, $LC_ALL, or $LC_CTYPE to a valid POSIX value 以及 A lot of character sets are supported now via a call to setlocale(). The [...]]]></description>
			<content:encoded><![CDATA[<p>Linux玩久了之后，在Windows下用不了很多优秀的命令行工具，就会感觉特别不爽。因此我一般都会在电脑上安装一套GNU utils for windows。最开始的时候，我用的是minGW里的utils，但是它附带的工具不全，虽然有一部分其他工具可以在其他地方google到，毕竟比较费神费力。</p>
<p>后来好好玩了一下Cygwin，开始喜欢上这个玩意儿了。以前一直以为cygwin就是开始菜单里启动的那个bash界面，后来发现其实不是，cygwin实际上是通过一个cygwin1.dll实现了几乎全部的UNIX函数，因此只要链接到这个dll，就可以很方便的port各种UNIX工具了。port出来的工具，也可以在DOS命令行下正常执行。</p>
<p>因为Cygwin的安装是集中式的，有点类似于apt机制，这很方便，不需要到处找各种各样的port了，只要从registry里安装就行。<span id="more-116"></span></p>
<p>于是我将绝大部分的命令行工具都通过cygwin安装使用，然后在PATH里填上cygwin/bin的路径就行了，非常方便。包括原来在Windows下单独安装的python、hg等工具，都换成了cygwin版本的。</p>
<p>但是最近在做一个工作上用的数据导入工具的时候，麻烦来了。</p>
<p>因为导入文件有可能是中文文件名，或者存在于中文路径中，我发现一旦涉及中文，cygwin版的python输出就是乱码，后来进一步发现，python里直接输出的中文也是有问题的。</p>
<p>然后，我联想到使用cygwin命令的时候，对中文的处理也是不尽人意的，比如ls如果碰到中文文件名，就会输出成一堆问号。</p>
<p>这次我想干脆将这些问题一起搞定。</p>
<p>于是就开始了找资料之旅。</p>
<p>网上很多处理cygwin中文的资料，都是针对bash的，然而我对cygwin的使用，在绝大部分情况下都不涉及bash。因此肯定要寻求其他途径。</p>
<p>然后又找到一个日本人改造的<a href="http://www.okisoft.co.jp/esc/utf8-cygwin/">cygwin的utf8版</a>。这个倒是有用，不过他做的是1.5版的cygwin，而我现在使用的是1.7beta版的。降级，这个事情我可有点不甘愿。先放着吧，实在不行再采取这个方案。</p>
<p>google了一大圈无果，今天突然想起来去cygwin的官方网站瞧瞧。没想到这一瞧还真有收获，在1.7版的<a href="http://cygwin.com/1.7/cygwin-ug-net/ov-new1.7.html">new feature</a>中，赫然写着</p>
<blockquote><p>To always have a valid string, use the UTF-8 charset by setting the environment variable $LANG, $LC_ALL, or $LC_CTYPE to a valid POSIX value</p></blockquote>
<p>以及</p>
<blockquote><p>A lot of character sets are supported now via a call to setlocale().<br />
The setting of the environment variables $LANG, $LC_ALL or $LC_CTYPE will  be used.</p></blockquote>
<p>于是兴冲冲的在命令行下输入： set LC_ALL=zh_CN.GBK，然后再输入 ls，熟悉的中文文件名终于出现在了眼前。真简单，不是么？</p>
<p>在第一个胜利的鼓舞下，我再接再厉，设置好LC_ALL之后输入python，然后尝试在一个中文路径中 import os; print os.getcwd() ，果不其然，我得到了…………一堆乱码 囧。</p>
<p>为什么locale对python程序无效呢？难道python有特别的设置？再找资料，在python的locale module一节看到了一段话：</p>
<blockquote><p>Initially, when a program is started, the locale is the <tt><span class="pre">C</span></tt> locale, no matter what the user’s preferred locale is.  The program must explicitly say that it wants the user’s preferred locale settings by calling <tt><span class="pre">setlocale(LC_ALL,</span> <span class="pre">'')</span></tt>.</p></blockquote>
<p>翻译过来就是，当一个程序启动的时候，locale一定会被设置成C，无论你在环境里的设置是什么。如果需要使用环境设置，你必须显式的调用 locale.setlocale(locale.LC_ALL, '')</p>
<p>好吧，在我的程序里加上这一句，果然，中文路径的输出结果正常了。但是……，在程序里主动输出的信息仍然是乱码。而且当发生异常时，异常信息也是乱码。</p>
<p>经过一段胡折瞎腾，终于发现，当locale设置成zh_CN.GBK时，要将输出信息编码成UTF-8，输出才不会乱码。可是……，如果真这么改的话，用windows版的python运行时，信息又变成乱码了。我不能写这种跟运行环境相关的代码啊，毕竟像我这样用cygwin python的变态不会太多的。</p>
<p>于是继续找资料，直觉告诉我这个问题应该跟stdout的encoding有关，于是找这几个关键字：stdout、encode、codecs。经过一番努力，还真的找到了结果：我们可以根据locale来设定stdout的encoding：</p>
<pre>import codecs
sys.stdout = codecs.getreader(locale.getpreferredencoding())(sys.stdout)
sys.stdin = codecs.getreader(locale.getpreferredencoding())(sys.stdin)</pre>
<p>这样一来，就可以正常输出程序中书写的中文信息，而无需任何特殊转换了。</p>
<p>经过这样一番努力，基本上解决我99%的问题：cygwin的工具可以正常使用中文、python可以正常使用中文，异常信息中的中文也可以正常输出。现在唯一没有解决的就是，当异常没有被捕获时，traceback的输出信息里，中文路径依然是乱码，似乎没有被locale设定所控制。不过因为这一点并不影响我的使用，暂时先不管了。</p>
<p>总结一下，在cygwin以及cygwin python中正常使用中文的做法：</p>
<ul>
<li>在“我的电脑”点右键，属性，高级，环境变量。增加一个环境变量 LC_ALL，值是zh_CN.GBK</li>
<li>打开命令行，这时cgywin的工具应该都可以正常使用了。</li>
<li>在我们的python程序开头增加这样一段代码：</li>
</ul>
<pre>#set locale environment
import locale
import codecs
locale.setlocale(locale.LC_ALL, '')    #将环境中的locale设定沿用到python程序中
sys.stdout = codecs.getreader(locale.getpreferredencoding())(sys.stdout)
sys.stdin = codecs.getreader(locale.getpreferredencoding())(sys.stdin)  #根据locale设置指定标准输入输出的编码</pre>
<p>这样可以使得程序在native python和cygwin python中都没有中文问题（99%的情况下）。</p>
<p>另外要注意一点的是，在python程序中，对locale的改变请务必放在主程序中而不要放在module中，否则可能会造成module和主程序的locale设定混乱，引起不可预期的问题。在主程序中对locale的设定会自动沿用到各个module中的。</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=116</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>试了一把PCRE</title>
		<link>http://legacy.go4pro.org/?p=114</link>
		<comments>http://legacy.go4pro.org/?p=114#comments</comments>
		<pubDate>Sun, 12 Jul 2009 02:16:36 +0000</pubDate>
		<dc:creator>猛禽</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[BCB]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[pcre]]></category>
		<category><![CDATA[text]]></category>
		<category><![CDATA[正则表达式]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=114</guid>
		<description><![CDATA[虽然我在C++里用正则表达式已经有一些年头了，不过一直都是用的boost里那个库。坦白说，不是很好用。虽然我很早就知道PCRE，但一直都以为这是一个为PHP开发的库。实在是火星人啊。囧 前两天在推土上提起这事时，火炬向我推荐说PCRE比boost里那个正则库好用，于是试了一下，结果可耻滴发现BCB自带了PCRE，只不过没有在文档里提到罢了。 不 过PCRE是一个C语言的库，用起来不够方便。虽然也有PCRE++这种C++封装的版本，但是只提供了GNU编译配置，移植到BCB里估计比较麻烦，因 为我用到的功能也不多，就自己做了个简单的封装，用了一些VCL的AnsiString/StringList之类。用起来方便不少。 #include &#60;pcre.h&#62; class TPCRE { private: AnsiString FPattern; pcre * FRE; TStrings * FMatches; public: __fastcall TPCRE(AnsiString aPattern=""); __fastcall ~TPCRE(); void __fastcall compile(AnsiString aPattern=""); int __fastcall exec(AnsiString aStr); // return matched count AnsiString __fastcall repeat_replace(AnsiString aStr, AnsiString aRepStr=""); __property TStrings * Matches = { read = FMatches }; }; __fastcall [...]]]></description>
			<content:encoded><![CDATA[<p>虽然我在C++里用正则表达式已经有一些年头了，不过一直都是用的boost里那个库。坦白说，不是很好用。虽然我很早就知道PCRE，但一直都以为这是一个为PHP开发的库。实在是火星人啊。囧</p>
<p>前两天在推土上提起这事时，火炬向我推荐说PCRE比boost里那个正则库好用，于是试了一下，结果可耻滴发现BCB自带了PCRE，只不过没有在文档里提到罢了。</p>
<p><span id="more-114"></span>不 过PCRE是一个C语言的库，用起来不够方便。虽然也有PCRE++这种C++封装的版本，但是只提供了GNU编译配置，移植到BCB里估计比较麻烦，因 为我用到的功能也不多，就自己做了个简单的封装，用了一些VCL的AnsiString/StringList之类。用起来方便不少。</p>
<pre>#include &lt;pcre.h&gt;

class TPCRE
{
private:
    AnsiString FPattern;
    pcre * FRE;
    TStrings * FMatches;

public:
  __fastcall TPCRE(AnsiString aPattern="");
  __fastcall ~TPCRE();

  void __fastcall compile(AnsiString aPattern="");
  int  __fastcall exec(AnsiString aStr);  //  return matched count
  AnsiString __fastcall repeat_replace(AnsiString aStr, AnsiString aRepStr="");

  __property TStrings * Matches = { read = FMatches };
};

__fastcall TPCRE::TPCRE(AnsiString aPattern)
    : FRE(NULL), FMatches(new TStringList())
{
    FPattern = aPattern;
    if ( FPattern != "" )
        compile();
}

__fastcall TPCRE::~TPCRE()
{
    if (FRE)
        free(FRE);
    delete FMatches;
}

void __fastcall TPCRE::compile(AnsiString aPattern)
{
    if ( aPattern != "" )
        FPattern = aPattern;
    const char * error;
    int erroffset;
    if (FRE)
        free(FRE);
    FRE = pcre_compile(FPattern.c_str(), 0, &amp;error, &amp;erroffset, NULL);
    // if ( FRE == NULL )
    // PCRE compilation failed at offset %d: %s, erroffset, error
}

int __fastcall TPCRE::exec(AnsiString aStr)
{
    if (!FRE)
        throw Exception("No pattern or have not be compiled!");
    const int OVECCOUNT = 30;
    int ovector[OVECCOUNT];
    int rc = pcre_exec(FRE, NULL, aStr.c_str(), aStr.Length(), 0, ovector, OVECCOUNT);
    if (rc &lt; 0) {
        if (rc == PCRE_ERROR_NOMATCH)
            throw Exception("Sorry, no match ...");
        else
            throw Exception(AnsiString("Matching error ") + IntToStr(rc));
    }
    // OK, has matched ...
    FMatches-&gt;Clear();
    for (int i = 0; i &lt; rc; i++)
        FMatches-&gt;Add(aStr.SubString(
            ovector[2*i]+1, ovector[2*i+1]-ovector[2*i]));
    return rc;
}

AnsiString __fastcall TPCRE::repeat_replace(AnsiString aStr, AnsiString aRepStr)
{
    if (!FRE)
        throw Exception("No pattern or have not be compiled!");
    const int OVECCOUNT = 30;
    int ovector[OVECCOUNT];
    int rc=1;
    char *p = aStr.c_str();
    int n = 1;
    int len = aStr.Length();
    AnsiString s="";
    while (rc&gt;0) {
        rc = pcre_exec(FRE, NULL, p, len, 0, ovector, OVECCOUNT);
        if (rc &lt; 0) {
            if (rc == PCRE_ERROR_NOMATCH) {
                if (s=="")
                    s = aStr;
                else
                    s += aStr.SubString(n,aStr.Length()-n+1);
                break;
            }
            else
                throw Exception(AnsiString("Matching error ") + IntToStr(rc));
        }
        // OK, has matched ...
        s += aStr.SubString(n,ovector[0])+aRepStr;
        n += ovector[1];
        p = aStr.c_str()+n-1;
        len = aStr.Length()-n+1;
    }
    return s;
}</pre>
<p>用法很简单，这是一段示例代码，可以把HTML转换成TXT：</p>
<pre>// 输入：const char *s
// 输出：AnsiString sResult
    std::auto_ptr&lt;TPCRE&gt; re(new TPCRE("(?ims)&lt;title&gt;([^&lt;]*)"));
    AnsiString sResult;
    if (re-&gt;exec(s)&gt;1)
        sResult = re-&gt;Matches-&gt;Strings[1].Trim();
    re-&gt;compile("(?ims)&lt;body[^&gt;]*&gt;(.*)");
    if (re-&gt;exec(s)&gt;1) {
        AnsiString str = re-&gt;Matches-&gt;Strings[1].Trim();
        str = StringReplace(str,"\r","",TReplaceFlags()&lt;&lt;rfReplaceAll);
        str = StringReplace(str,"\n","",TReplaceFlags()&lt;&lt;rfReplaceAll);
        str = StringReplace(str,"&amp;nbsp;"," ",TReplaceFlags()&lt;&lt;rfReplaceAll);
        //  replace &lt;br /&gt; to \r\n
        re-&gt;compile("(?i)&lt;br\s*/?&gt;");
        str = re-&gt;repeat_replace(str,"\r\n");
        //  remove &lt;script...&gt;...&lt;/script&gt;
        re-&gt;compile("(?ims)&lt;script[^&gt;]*&gt;.*&lt;/script&gt;");
        str = re-&gt;repeat_replace(str);
        //  remove &lt;!-- ... --&gt;
        re-&gt;compile("(?ims)&lt;!--.*--&gt;");
        str = re-&gt;repeat_replace(str);
        //  remove &lt;...&gt;
        re-&gt;compile("(?ims)&lt;[^&gt;]*&gt;");
        str = re-&gt;repeat_replace(str);
        sResult += str;
    }</pre>
<p>主要就是取出title部分和body部分，然后将body部分的回车全部去掉，&amp;nbsp;替换成空格，br换成回车，脚本和注释去掉，最后去掉所有HTML的标记。</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=114</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>用RTTI处理程序配置信息</title>
		<link>http://legacy.go4pro.org/?p=112</link>
		<comments>http://legacy.go4pro.org/?p=112#comments</comments>
		<pubDate>Sun, 12 Jul 2009 02:14:01 +0000</pubDate>
		<dc:creator>猛禽</dc:creator>
				<category><![CDATA[Delphi]]></category>
		<category><![CDATA[RTTI]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=112</guid>
		<description><![CDATA[一般来说，程序多少都会有一些自己的配置信息要保存，不论是通常用的保存到INI还是注册表，或者是XML甚至YAML，总归是要写不少代码处理的。 问题的麻烦在于，程序中实现操作配置信息通常并不需要关注它是保存在什么地方，以什么格式保存，但是传统的方法不论是调用TIniFile还是TRegistry或者是DOM，都是需要开发者花一些时间精力浪费在这上面。 我前一阵写个小程序，不高兴弄配置这些麻烦事，于是利用前些年写的一个框架代码，实现了一套通用的配置序列化机制，这样配置信息管理起来就方便多了。 代码详见MDConfig单元。 基本组成是这样： TMCustomConfigSection 类实现了IMSerializable接口（定义在框架的MDComm中），意味着它可以被递归序列化——不过在已实现的INI文件和注册表文件序列化 中，这种递归仅限于一层：即一个配置文件可以包括多个配置段，但是配置段下不能再有配置段。当然，框架中实现的通用序列化（二进制和XML）不受此限。 TMCustomConfig类则是可序列化的配置文件，派生自TPersistent。这个类里除了可以包含配置信息以外，还可以包括配置段。 TMCustomConfigSerializer类则是配置序列化基类，实现配置序列化的公共部分——即与具体序列化方式无关的部分。 TMIniSerializer类是INI文件的序列化类，通过它可以把配置文件序列化到INI文件中去。 TMRegSerializer类是注册表序列化类。 配置的基本使用方法是这样的： 分别从TMCustomConfigSection和TMCustomConfig派生自己的配置段类和配置类，在其中定义配置项目供程序使用。示例代码如： TMTestConfigSection = class(TMCustomConfigSection) Private FStringItem : String; FIntItem : Integer; FFloatItem : Double; FDateItem : TDateTime; Published Property StringItem : String Read FStringItem Write FStringItem; Property IntItem : Integer Read FIntItem Write FIntItem; Property FloatItem : Double Read FFloatItem Write FFloatItem; [...]]]></description>
			<content:encoded><![CDATA[<p>一般来说，程序多少都会有一些自己的配置信息要保存，不论是通常用的保存到INI还是注册表，或者是XML甚至YAML，总归是要写不少代码处理的。</p>
<p>问题的麻烦在于，程序中实现操作配置信息通常并不需要关注它是保存在什么地方，以什么格式保存，但是传统的方法不论是调用TIniFile还是TRegistry或者是DOM，都是需要开发者花一些时间精力浪费在这上面。</p>
<p><span id="more-112"></span>我前一阵写个小程序，不高兴弄配置这些麻烦事，于是利用前些年写的一个框架代码，实现了一套通用的配置序列化机制，这样配置信息管理起来就方便多了。</p>
<p>代码详见MDConfig单元。</p>
<p>基本组成是这样：</p>
<p>TMCustomConfigSection 类实现了IMSerializable接口（定义在框架的MDComm中），意味着它可以被递归序列化——不过在已实现的INI文件和注册表文件序列化 中，这种递归仅限于一层：即一个配置文件可以包括多个配置段，但是配置段下不能再有配置段。当然，框架中实现的通用序列化（二进制和XML）不受此限。</p>
<p>TMCustomConfig类则是可序列化的配置文件，派生自TPersistent。这个类里除了可以包含配置信息以外，还可以包括配置段。</p>
<p>TMCustomConfigSerializer类则是配置序列化基类，实现配置序列化的公共部分——即与具体序列化方式无关的部分。</p>
<p>TMIniSerializer类是INI文件的序列化类，通过它可以把配置文件序列化到INI文件中去。</p>
<p>TMRegSerializer类是注册表序列化类。</p>
<p>配置的基本使用方法是这样的：</p>
<p>分别从TMCustomConfigSection和TMCustomConfig派生自己的配置段类和配置类，在其中定义配置项目供程序使用。示例代码如：</p>
<pre>    TMTestConfigSection = class(TMCustomConfigSection)
    Private
        FStringItem : String;
        FIntItem : Integer;
        FFloatItem : Double;
        FDateItem : TDateTime;
    Published
        Property StringItem : String Read FStringItem Write FStringItem;
        Property IntItem : Integer Read FIntItem Write FIntItem;
        Property FloatItem : Double Read FFloatItem Write FFloatItem;
        Property DateItem : TDateTime Read FDateItem Write FDateItem;
    End;

    TMTestConfig = class(TMCustomConfig)
    Private
        FSectionItem : TMTestConfigSection;
        FStringItem : String;
    public
        Constructor Create(aDefaultSerializer : IMSerializer = Nil);
        destructor Destroy; Override;
    Published
        Property SectionItem : TMTestConfigSection Read FSectionItem Write FSectionItem;
        Property StringItem : String Read FStringItem Write FStringItem;
    End;

Constructor TMTestConfig.Create(aDefaultSerializer : IMSerializer = Nil);
begin
  Inherited Create(aDefaultSerializer);
    FSectionItem := TMTestConfigSection.Create();
end;

destructor TMTestConfig.Destroy;
begin
    FSectionItem.Free;
  Inherited
end;</pre>
<p>序 列化方式则可以自由选择目前已经实现的四种：INI文件，注册表，二进制文件，XML。序列化操作也可以选择默认序列化或是指定序列化。默认序列化就是在 Config对象构造时传入默认的序列化方式，指定序列化就是在调用Config对象的Load/Save方法时指定序列方式。当然，即使设置了默认序列 化方式，也是可以在调用Load/Save方法时重新指定的。</p>
<p>默认序列化：</p>
<pre>  conf := TMTestConfig.Create(TMIniSerializer.Create('e:\temp\test.ini'));
  conf.StringItem := 'This is default section item';
  conf.SectionItem.StringItem := 'This is Section item';
  conf.SectionItem.IntItem := 12345;
  conf.SectionItem.FloatItem := 9.87654;
  conf.SectionItem.DateItem := EncodeDate( 2009, 01, 02 );
  conf.Save;</pre>
<p>指定序列化：</p>
<pre>  conf := TMTestConfig.Create;
  conf.Load(TMIniSerializer.Create('e:\temp\test.ini'));
  conf.Save(TMRegSerializer.Create(HKEY_CURRENT_USER, 'software\mental studio\devotee\test'));</pre>
<p>注意，其中序列化器是通过引用计数维护的，会自动释放，不需要显示释放。</p>
<p><a href="http://mental.we8log.com/mental/entry/184">mdf框架全部源码下载</a>。</p>
<p>只使用配置序列化部分的话，只需要MDComm和MDConfig两个单元文件（二进制和XML序列化需要MSerializ单元）。</p>
<p>在BCB中使用的特别注意事项：</p>
<p>要用一个Delphi单元来定义配置类，因为用到了Delphi的RTTI，虽然用C++来写配置类也可以编译，但是会发生运行时错误。特别关键的是序列化器对象必须在Delphi单元中创建。形如：</p>
<pre>Constructor TMainConfig.Create(aIniName : String);
begin
  Inherited Create(TMIniSerializer.Create(aIniName, 'Default'));
end;

//  然后在BCB中这样使用

TMainConfig * MainCfg = new TMainConfig(ChangeFileExt(Application-&gt;ExeName,".ini"));</pre>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=112</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>【jQuery】示例5：一个简单的投票系统</title>
		<link>http://legacy.go4pro.org/?p=110</link>
		<comments>http://legacy.go4pro.org/?p=110#comments</comments>
		<pubDate>Sat, 09 May 2009 23:32:21 +0000</pubDate>
		<dc:creator>TR@SOE</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[demos]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[jquery api browser]]></category>
		<category><![CDATA[例子]]></category>

		<guid isPermaLink="false">http://www.go4pro.org/?p=110</guid>
		<description><![CDATA[今天抽空完成了jQuery的示例5：一个简单的投票系统。 本例没有什么特殊的地方。只有一点。投票系统至少需要两个参数：一个是投票项目的id，一个是投票的方向（支持还是反对），所以我这样构造一个a元素： 我用class来确定投票方向，而用id来确定投票项目。 最后，我用jQuery中的replaceWith来替代掉被点击的a元素的内容为： $(this).replaceWith(""+count+""); 大功告成！]]></description>
			<content:encoded><![CDATA[<p>今天抽空完成了jQuery的示例5：<a href="http://www.rsywx.net/jquery/demos/test05.php">一个简单的投票系统</a>。</p>
<p>本例没有什么特殊的地方。只有一点。投票系统至少需要两个参数：一个是投票项目的id，一个是投票的方向（支持还是反对），所以我这样构造一个a元素：</p>
<pre lang="php">
<a href='#' class='up' id='<?php echo $row['vid']?>'><?php echo $row['up']?></a>
</pre>
<p>我用class来确定投票方向，而用id来确定投票项目。</p>
<p>最后，我用jQuery中的replaceWith来替代掉被点击的a元素的内容为：</p>
<pre lang="javascript">
$(this).replaceWith("<strong>"+count+"</strong>");
</pre>
<p>大功告成！</p>
]]></content:encoded>
			<wfw:commentRss>http://legacy.go4pro.org/?feed=rss2&amp;p=110</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

