克己服人,礼智谦让

Welcome to lhelper's weblog

首页 文章 Blogs 项目 相册 书签 爱好 站点地图    

Friday, October 28, 2005

replace the default Tokenizer of carrot2 lingo filter with ICTCLAS

ICTCLAS(Institute of Computing Technology, Chinese Lexical Analysis System,中国科学院计算技术研究所汉语词法分 析系统)替换nutch (0.7 release) 中carrot2 lingo filter 的Tokenizer。

nutch (0.7 release) 中的clustering plugin 采用的是Dawid Weiss 主持开发的carrot2, 主要用到了其中的local controller 和 lingo filter 两个 compenent。lingo filter 缺省的Tokenizer (com.stachoodev.carrot.filter.lingo.tokenizer.Tokenizer)缺乏对中文的分词 处理,这使得用这个filter 处理中文结果时效果往往不很让人满意! 于是开始为nutch 选择合适的中文分词模块,经过一周左右的挑选、分 析和比较,最终决定采用中国科学院计算技术研究所汉语词法分析系 统——ICTCLAS。

接下来用了一周多的学习和练习:

  • 如何用java 的jni 接口调用native method
  • 如何用iconv 实现字符的编码转换
  • 如何为ICTCLAS 编写Makefile
  • 如何在resin/tomcat 下调用ICTCLAS方法
应该说这一周的学习历程是相当痛苦的,因为在这个过程中遇到的大部分细节问题都 没办法直接从google 中找到答案,只能从类似问题的解决方法中寻 找线索。例如,由于每个应用服务器对子进程的stack size 限制的 不同,导致native method 经常会抛出有关 memset() 的异常。

经过一周多的时间终于用ICTCLAS 调换掉了lingo filter 缺省的 Tokenizer (com.stachoodev.carrot.filter.lingo.tokenizer.Tokenizer) :*)

nutch (0.7 release) 中carrot2 的版本略低 于carrot2 官方网站对外公布的版本。

Wednesday, September 28, 2005

swith on the clustering plugin of nutch

the following works for the 0.7 release

edit $NUTCH_HOME/WEB-INF/classes/nutch-default.xml

  • register 'clustering-carrot2' plugin
  • 
    <property>
      <name>plugin.includes</name>
      <value>clustering-carrot2|protocol-httpclient|urlfilter-regex|parse-(text|html|js)|index-basic|query-(basic|site|url)</value>
       ...
    </property>
    
    
  • set attribute 'extension.clustering.extension-name'
  • 
    <property>
      <name>extension.clustering.extension-name</name>
      <value>Carrot2-Lingo</value>
      ...
    </property>
    
    

Carrot2-Lingo: see $NUTCH_HOME/plugins/clustering-carrot2/plugin.xml for detail.

Now, it's ok! add an parameter 'clustering=yes' onto your search url and try it.

Tuesday, September 27, 2005

hack NutchAnalysis.jj further so as to perform third-part segmentation

nutch (0.7 release) 缺省的token 规则对于很多语言(如CJK、 Myanmar等等)来说都不是很适合,于是很多人尝试在 NutchAnalysis.jj 中嵌入自己实现的Tokenizer,比较成功的例子有 Chinese in Nutch:My solution

下面是我想到的一个蹩脚方法,之所以说这个方法蹩脚是因为它在执行切 分过程中产生了 sizeof(tokens[])次input_stream.backup(1) 操作和副作用:-(。 但这个方法也还是有些可取之处的——把不同语言的切分工作 交给不同的Tokenizer 去处理,这样可以产生比较好的细粒度切 分效果。

org.apache.nutch.analysis.NutchAnalysis.jj


@@ -33,6 +33,7 @@
 import org.apache.nutch.searcher.Query.Clause;
 
 import org.apache.lucene.analysis.StopFilter;
+import org.apache.lucene.analysis.cjk.CJKTokenizer;
 
 import java.io.*;
 import java.util.*;
@@ -81,6 +82,14 @@
 PARSER_END(NutchAnalysis)
 
 TOKEN_MGR_DECLS : {
+ /** use CJKTokenizer to process cjk character */
+ private CJKTokenizer cjkTokenizer = null;
+
+ /** a global cjk token */
+ private org.apache.lucene.analysis.Token cjkToken = null;
+
+ /** start offset of cjk sequence */
+ private int cjkStartOffset = 0;
 
   /** Constructs a token manager for the provided Reader. */
   public NutchAnalysisTokenManager(Reader reader) {
@@ -106,7 +115,46 @@
     }
 
   // chinese, japanese and korean characters
-| <SIGRAM: <CJK> >
+| <SIGRAM: (<CJK>)+ >
+ {
+   /**
+   * use an instance of CJKTokenizer, cjkTokenizer, hold the maximum
+   * matched cjk chars, and cjkToken for the current token;
+   * reset matchedToken.image use cjkToken.termText();
+   * reset matchedToken.beginColumn use cjkToken.startOffset();
+   * reset matchedToken.endColumn use cjkToken.endOffset();
+   * backup the last char when the next cjkToken is valid.
+   */
+   if(cjkTokenizer == null) {
+     cjkTokenizer = new CJKTokenizer(new StringReader(image.toString()));
+     cjkStartOffset = matchedToken.beginColumn;
+     try {
+       cjkToken = cjkTokenizer.next();
+     } catch(IOException ioe) {
+       cjkToken = null;
+     }
+   }
+
+   if(cjkToken != null && !cjkToken.termText().equals("")) {
+     //sometime the cjkTokenizer returns an empty string, is it a bug?
+     matchedToken.image = cjkToken.termText();
+     matchedToken.beginColumn = cjkStartOffset + cjkToken.startOffset();
+     matchedToken.endColumn = cjkStartOffset + cjkToken.endOffset();
+     try {
+       cjkToken = cjkTokenizer.next();
+     } catch(IOException ioe) {
+       cjkToken = null;
+     }
+     if(cjkToken != null && !cjkToken.termText().equals("")) {
+       input_stream.backup(1);
+     }
+   }
+
+   if(cjkToken == null || cjkToken.termText().equals("")) {
+     cjkTokenizer = null;
+     cjkStartOffset = 0;
+   }
+ }

例子中所用的CJKTokenizer 取自Weblucene

关键词:nutch, segmentation, chinese, cjk

参考:

Thursday, September 22, 2005

hack NutchAnalysis.jj so as to perform bi-gram segmentation

Doug Cutting 的启发,修改NutchAnalysis.jj 如下,简单实现了二 元切分功能:*)

109,110c109,112
< | <SIGRAM: <CJK> >
< 
---
> | <SIGRAM: <CJK><CJK> >
>   {
>     input_stream.backup(1);
>   }

简单测试一下效果:
$ echo $LANG
en_US.UTF-8
$ LANG=zh_CN
$ ./bin/nutch org.apache.nutch.analysis.NutchDocumentTokenizer
Text: 克己服人,礼智谦让
Tokens: 克己 己服 服人 礼智 智谦 谦让
Text: 中华人民共和国
Tokens: 中华 华人 人民 民共 共和 和国
Text: 中文&English&英文
Tokens: 中文 &english& 英文
Text: 写这些内容的时候,作者运行的是nutch 的0.7 release;如果你运行的是nutch 的其他版本,那么下面的内容可能并不适合你:
Tokens: 写这 这些 些内 内容 容的 的时 时候 作者 者运 运行 行的 的是 nutch 0 7 release 如果 果你 你运 运行 行的 的是 nutch 的其 其他 他版 版本 那么 么下 下面 面的 的内 内容 容可 可能 能并 并不 不适 适合 合你

关键词: nutch, segmentation, chinese, cjk, bi-gram

参考:

Wednesday, September 21, 2005

understand the tokenizer of nutch

逐步理解nutch 的 tokenizer(org.apache.nutch.analysis.NutchDocumentTokenizer)

注:写这些内容的时候,作者运行的是nutch 的0.7 release; 如果你运行的是nutch 的其他版本,那么下面的内容可能并不适合 你:*)

$ echo $LANG
en_US.UTF-8
$ LANG=zh_CN
$ ./bin/nutch org.apache.nutch.analysis.NutchDocumentTokenizer
Text: I.B.M.
Tokens: ibm
Text: I.B.M
Tokens: ib m
Text: AT&T
Tokens: at&t
Text: AT_T
Tokens: at_t
Text: 中华人民共和国
Tokens: 中 华 人 民 共 和 国
Text: 中文&English&英文
Tokens: 中 文 &english& 英 文

为什么会有这种切分效果呢?看看下面的 org.apache.nutch.analysis.NutchAnalysis.jj 文件片断也许就能明 白了:


TOKEN : {// token regular expressions
// basic word -- lowercase it
<WORD: ((<LETTER>|<DIGIT>|<WORD_PUNCT>)+ | <IRREGULAR_WORD>)>
  {
     matchedToken.image = matchedToken.image.toLowerCase();
  }
// special handling for acronyms: U.S.A., I.B.M., etc: dots are removed
| <ACRONYM: <LETTER> "." (<LETTER> ".")+ >
  {// remove dots
    for (int i = 0; i < image.length(); i++) {
      if (image.charAt(i) == '.')
        image.deleteCharAt(i--);
    }
    matchedToken.image = image.toString().toLowerCase();
  }
// chinese, japanese and korean characters
| <SIGRAM: <CJK> >
// irregular words
| <#IRREGULAR_WORD:(<C_PLUS_PLUS>|<C_SHARP>)>
......

当前的tokenizer 把单个的CJK 字符当成了一个完整的Token—— 并未对连续的CJK 字符做切分词处理,于是就出 现了上面的结果。

Tuesday, September 20, 2005

crawl-urlfilter.txt vs. regex-urlfilter.txt in nutch

crawl-urlfilter.txt 之于 regex-urlfilter.txt

注:写这些内容的时候,作者运行的是nutch 的0.7 release; 如果你运行的是nutch 的其他版本,那么你可能需要自己去分析 二者的区别:*)

首先有一种说法是:crawl-urlfilter.txt 是供intranet 抓取用的, 而regex-urlfilter.txt 是供internet 抓取用的——因为两者抓取 的重点不同,从而导致过滤规则不同。如果你只是想知道两者的简单区别 的话,那么看到这里也就可以了,但是如果你想知道更多细节那么下 面的内容还是值得一看的:)

crawl-urlfilter.txt 和 regex-urlfilter.txt 都是用来保存 过滤url 的正则表达式的。类 RegexURLFilter(org.apache.nutch.net.RegexURLFilter) 通过 NutchConf.get().get("urlfilter.regex.file") 来从中(当然如果 你装载了其他配置文件的话,也可能有其他的候选者)进行选择。

regex-urlfilter.txt 是 urlfilter.regex.file 属性的缺省值(1),定义在 $NUTCH_JAVA_HOME/conf/nutch-default.xml(如果你设置了环境变 量NUTCH_CONF_DIR 的话,那就是 $NUTCH_CONF_DIR/nutch-default.xml)中。 该值可以被后续加载的配置文件所覆盖,例如如果你装载了类 CrawlTool(org.apache.nutch.tool.CrawlTool),那么缺省值就会被 crawl-tool.xml 中的urlfilter.regex.file 属性覆盖(2)。也就是 说,如果你在操作过程中调用了类CrawlTool,那么类RegexURLFilter 将会采用crawl-tool.xml 中指定的文件,否则就用缺省的文件。

当然如果nutch-site.xml 中也定义了urlfilter.regex.file 属性的话,那么 NutchConf.get().get("urlfilter.regex.file") 返回的值就以nutch-site.xml 所指定的值为准。(关于nutch 的resource chain)

注:(1)NutchConf 初始化时会装载nutch-default.xml 和nutch-site.xml


public class NutchConf {
......
  public NutchConf() {
    resourceNames.add("nutch-default.xml");
    resourceNames.add("nutch-site.xml");
  }
......
}

(2)装载类CrawlTool 时,crawl-tool.xml 会被插入到resource chain 中

public class CrawlTool {
......
  static {
    NutchConf.get().addConfResource("crawl-tool.xml");
  }
......
}

参见:

Monday, September 19, 2005

hack URLFilters so as to test RegexURLFilter

只是简单给URLFilters(org.apache.nutch.net.URLFilters)加 了一个 main(String[]) 方法 :*)

public static void main(String args[]) throws IOException, MalformedPatternException, Exception {
  BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
  String line;
  while((line=in.readLine())!=null) {
    String out=URLFilters.filter(line);
    if(out!=null) {
      System.out.print("+");
      System.out.println(out);
    } else {
      System.out.print("-");
      System.out.println(line);
    }
  }
}

这样测试url filter rule 就方便多了:)


$ ./bin/nutch org.apache.nutch.net.URLFilters
......
http://www.lhelper.org/blog/   
using fitler 0:org.apache.nutch.net.RegexURLFilter
+http://www.lhelper.org/blog/
http://www.lhelper.org/blog/?q=lhelper
using fitler 0:org.apache.nutch.net.RegexURLFilter
-http://www.lhelper.org/blog/?q=lhelper

Sunday, September 18, 2005

fix bugs of nutch's demo webapp

修正nutch (0.7 release) 的demo 中的几个小bug。
  • bugs relative to i18n support
  • bugs in $NUTCH_WEBAPP/${language}/search.html

    
    22+ <base href="${base}/zh/">
    
    

  • bugs relative to deprecated URLEncoder.encode(String s)

    bugs in $NUTCH_WEBAPP/search.jsp:

    
    222-(<a href="../explain.jsp?<%=id%>&query=<%=URLEncoder.encode(queryString)%>"><i18n:message key="explain"/></a>)
    222+(<a href="../explain.jsp?<%=id%>&query=<%=URLEncoder.encode(queryString, "UTF-8")%>"><i18n:message key="explain"/></a>)
    
    

    
    226- "query="+URLEncoder.encode("site:"+hit.getDedupValue()+" "+queryString)
    226+ "query="+URLEncoder.encode("site:"+hit.getDedupValue()+" "+queryString, "UTF-8")
    
    

Thursday, September 1, 2005

data structure of nutch

parse_text 存放页面的文本内容

parse_data 存放ParseStatus, Title, Outlink[], metadata

Friday, June 3, 2005

Concrete implementations of session, application, etc

session 和 application 是哪个类的实例呀?如果你也有类似迷 惑的话,可以试试下面一段代码:

<%@ page contentType="text/html;charset=GBK"%>
The concrete implementaions:<br>
out: <b><%=out.getClass().getName()%></b><br>
session: <b><%=session.getClass().getName()%></b><br>
application: <b><%=application.getClass().getName()%></b>

下面是resin (2.1.8)的具体实现:


The concrete implementaions:
out: com.caucho.jsp.QJspWriter
session: com.caucho.http.session.SessionImpl
application: com.caucho.server.http.Application

tomcat (4.1.24)的具体实现:


The concrete implementaions:
out: org.apache.jasper.runtime.JspWriterImpl
session: org.apache.catalina.session.StandardSessionFacade
application: org.apache.catalina.core.ApplicationContextFacade

Thursday, June 2, 2005

静态include 伪指令 Vs. 动态jsp:include 标记

static include directive vs. dynamic jsp:include tag.

经常忘记<%@ include file="${file}"%> 与 <jsp:include page="${fiel}" /> 的区别:-(,所以决定好好记一下。

注意:网上已经有很多介绍两者的区别的文章,例如:

这些文章也讲解的相当清楚,但是如果你想进一步知道为什么的话, 那么你可以从这里开始:-)

接下来我们以分析servlet engine 生成的java 源文件的形式来分 析其中的原因,我们以几个非常简单的jsp 文件为例:

index.jsp


<%@ page contentType="text/html;charset=GBK"%>
<%@ include file="static.jsp"%>
<jsp:include page="dynamic.jsp"/>

index.jsp 包含/include 了两个文件,static.jsp 和 dynamic.jsp:

static.jsp


<%@ page contentType="text/html;charset=GBK"%>
i'm the static segment :*)<br>

dynamic.jsp


<%@ page contentType="text/html;charset=GBK"%>
<%out.println("i'm the dynamic one!<br>");%>

浏览servlet engine(以resin 2.1.8 为例)生成的java source file,可以看到resin 只生成了两个源文件:_index__jsp.java 和 _dynamic__jsp.java 分别对应index.jsp 和 dynamic.jsp, static.jsp 没有对应的源文件。为什么呢?

index.jsp --> _index__jsp.java


package _demo;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.http.*;
public class _index__jsp extends com.caucho.jsp.JavaPage{
  private boolean _caucho_isDead;
  
  public void _jspService(javax.servlet.http.HttpServletRequest request,
         javax.servlet.http.HttpServletResponse response)
    throws java.io.IOException, javax.servlet.ServletException {
    com.caucho.jsp.QPageContext
pageContext=(com.caucho.jsp.QPageContext)com.caucho.jsp.QJspFactory.create().
getPageContext(this, request, response, null, true, 8192, true);
    javax.servlet.jsp.JspWriter out=(javax.servlet.jsp.JspWriter) pageContext.getOut();
    javax.servlet.ServletConfig config=getServletConfig();
    javax.servlet.Servlet page=this;
    javax.servlet.http.HttpSession session=pageContext.getSession();
    javax.servlet.ServletContext application=pageContext.getServletContext();
    response.setContentType("text/html;charset=GBK");
    request.setCharacterEncoding("GBK");
    try {
      pageContext.write(_jsp_string0, 0, _jsp_string0.length);//(1)
      pageContext.write(_jsp_string1, 0, _jsp_string1.length);
      pageContext.include("dynamic.jsp");//(2)
    } catch (java.lang.Throwable _jsp_e) {
      pageContext.handlePageException(_jsp_e);
    } finally {
      JspFactory.getDefaultFactory().releasePageContext(pageContext);
    }
  }
  ......
  private static byte []_jsp_string1;
  private static byte []_jsp_string0;
  static {
    try {
      _jsp_string1="rni'm the static segment :*)<br>rn".getBytes("GBK");//(3)
      _jsp_string0="rn".getBytes("GBK");
    } catch (java.io.UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }
}

dynamic.jap -->_dynamic__jsp.java

package _demo;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.http.*;
public class _dynamic__jsp extends com.caucho.jsp.JavaPage{
  private boolean _caucho_isDead;
  
  public void _jspService(javax.servlet.http.HttpServletRequest request,
        javax.servlet.http.HttpServletResponse response)
    throws java.io.IOException, javax.servlet.ServletException {
    com.caucho.jsp.QPageContext pageContext=
      (com.caucho.jsp.QPageContext) com.caucho.jsp.QJspFactory.create()
        .getPageContext(this, request, response, null, true, 8192, true);
    javax.servlet.jsp.JspWriter out=(javax.servlet.jsp.JspWriter) pageContext.getOut();
    javax.servlet.ServletConfig config=getServletConfig();
    javax.servlet.Servlet page=this;
    javax.servlet.http.HttpSession session=pageContext.getSession();
    javax.servlet.ServletContext application=pageContext.getServletContext();
    response.setContentType("text/html;charset=GBK");
    request.setCharacterEncoding("GBK");
    try {
      pageContext.write(_jsp_string0, 0, _jsp_string0.length);
      out.println("i'm the dynamic one!<br>");
    } catch (java.lang.Throwable _jsp_e) {
      pageContext.handlePageException(_jsp_e);
    } finally {
      JspFactory.getDefaultFactory().releasePageContext(pageContext);
    }
  }
  ......
}

查看resin 生成的java 源文件后可以知道

  • static.jsp 是以<%@ include file="static.jsp"%> 方式引用 的,于是resin 把static.jsp 的代码内容加载到了index.jsp 对应的源 文件中,见(1)(3)——这也就是为什么每当static.jsp 被 修改了,index.jsp 就要被重新编译一次的原因(当然一般情况下 servlet engine 可以自动帮你完成编译过程)——如果被引用的部 分经常被修改,就会导致index.jsp 经常被重新编译,class 被重新 装载,所以这种方式适 合于引用那些几乎不会变化的页面模块,例如页头、页尾;
  • dynamic.jsp 是以<jsp:include page="dynamic.jsp"/>方式 引用的,于是resin 把dynamic.jsp 生成的内容加载到了index.jsp 对应的 源文件中,见(2)。 下面是QPageContext.include(String)的具体实现:
    
    /**
     * Include another servlet into the current output stream.
     * @param relativeUrl url relative to the current request.
     */
    public void include(String relativeUrl)
      throws ServletException, IOException  {
      RequestDispatcher rd = null;
      if (relativeUrl != null && ! relativeUrl.startsWith("/")) {
        String path = request.getPageServletPath();
        if (path == null)
          path = request.getPagePathInfo();
        if (path == null)
          path = "/";
        int p = path.lastIndexOf('/');
        if (p >= 0) {
          path = path.substring(0, p + 1) + relativeUrl;
          rd = application.getRequestDispatcher(path);
        }
      }
      if (rd == null)
        rd = request.getRequestDispatcher(relativeUrl);
      if (rd == null)
        throw new ServletException(L.l("unknown including page `{0}'.", relativeUrl));
      rd.include(request, response);
    }
    
    

Tuesday, May 31, 2005

ICP 备案成功

终于赶在6.1之前备案了我的lhelper.org。

Message From 7026:
尊敬的用户,您的ICP备案信息注册成功!用户名:lhelper,手机验 证码:********,详细信息见您的邮件。

Wednesday, May 25, 2005

rectangle commands in Emacs

UltraEdit 提供的列编辑模式有些时候会让人觉得非常方便。发现 Emacs 是存在rectangle command

clear-rectangle               C-x r c
  Command: Blank out the region-rectangle.
close-rectangle               M-x close-rectangle RET
  Command: Delete all whitespace following a specified column in each line.
copy-rectangle-to-register    C-x r r
  Command: Copy rectangular region into register REGISTER.
delete-rectangle              C-x r d
  Command: Delete (don't save) text in the region-rectangle.
delete-whitespace-rectangle   M-x delete-whitespace-rectangle RET
  Command: Delete all whitespace following a specified column in each line.
delimit-columns-rectangle     M-x delimit-columns-rectangle RET
  Command: Prettify all columns in a text rectangle.
kill-rectangle                C-x r k
  Command: Delete the region-rectangle and save it as the last killed one.
open-rectangle                C-x r o
  Command: Blank out the region-rectangle, shifting text right.
replace-rectangle             M-x replace-rectangle RET
  Command: Replace rectangle contents with STRING on each line.
string-insert-rectangle       M-x string-insert-rectangle RET
  Command: Insert STRING on each line of region-rectangle, shifting text right.
string-rectangle              C-x r t
  Command: Replace rectangle contents with STRING on each line.
yank-rectangle                C-x r y
  Command: Yank the last killed rectangle with upper left corner at point.

Friday, May 20, 2005

echo Hex/Oct code in shell

用echo 生成Hex(十六进制)/Oct(八进制) 代码:

十六进制代码:
$ echo -e "\xd6\xd0\xce\xc4"
中文

八进制代码:
$ echo -e "\326\320\316\304"
中文

问:用echo生成这些Hex/Oct代码有什么用吗?
答:在处理xml 、文件或数据流的时候经常会遇到要处理特殊字符的问题,如非 法的xml字符、出现在文件或数据流中间的文件结束符等等,用echo (当然如果你有更好用的Hex/十六进制编辑器,或者其他什么编辑器 当然也是可以的)制造些这样的特殊字符插入到要处理的数据当中对程序进行测试, 可以使你的程序更安全:*)

附:

  • 非法的xml 字符(illegal xml character):
  • \x00-\x08
    \x0b-\x0c
    \x0e-\x1f
  • 文件结束符:

  • \00

参见:
$ man echo
-e enable interpretation of the backslash-escaped characters listed below
-E disable interpretation of those sequences in STRINGs
Without -E, the following sequences are recognized and interpolated:
\NNN the character whose ASCII code is NNN (octal)
\\ backslash
\a alert (BEL)
\b backspace
\c suppress trailing newline
\f form feed
\n new line
\r carriage return
\t horizontal tab
\v vertical tab

Thursday, May 19, 2005

用 Element.detach() 或 removeContent() 剥离xml节点

在用JDOM/DOM4J 处理xml 的时候 The element already has an existing parent


org.jdom.IllegalAddException: The element already has an existing parent "subject"
	at org.jdom.ContentList.add(ContentList.java:177)
	at org.jdom.ContentList.add(ContentList.java:138)
	at org.jdom.ContentList$FilterList.add(ContentList.java:756)
    ....

Wednesday, May 18, 2005

可用于查看http request/response header 的工具

在开发web 应用的过程中有时需要查看请求(request)/响应 (response) 中所含的信息头(header),用来验证程序/服务器的状态 是否正常。下面是我经常使用的几款小工具:
  1. GNU wget
  2. GNU wget 有很多特性,不仅可以用于传输文件、镜像站点,还可 以用于查看服务器端响应时response 的 header。

    $ wget -S --delete-after "http://dev.lhelper.org:8080/examples/basic/env.jsp?foo=bar"

    headers in request:
    
    User-Agent : Wget/1.8.2
    Host : dev.lhelper.org:8080
    Accept : */*
    Connection : Keep-Alive
    
    
    headers in response:
    
    HTTP request sent, awaiting response...
     1 HTTP/1.0 200 OK
     2 Server: Resin/2.1.8
     3 Set-Cookie: JSESSIONID=a8eAq6B6O_U7; path=/
     4 Content-Type: text/html
     5 Content-Length: 1263
     6 Date: Sun, 05 Jun 2005 23:39:32 GMT
    
    

    注:GNU wget 有些已知的安全漏洞,在使用的过程中需要注意!

  3. Lynx
  4. $ lynx -head -mime_header "http://dev.lhelper.org:8080/examples/basic/env.jsp?foo=bar"
    or
    $ lynx -mime_header "http://dev.lhelper.org:8080/examples/basic/env.jsp?foo=bar" > header_body.txt

    headers in request:
    
    Host : dev.lhelper.org:8080
    Accept : text/html, text/plain, text/sgml, */*;q=0.01
    Accept-Encoding : gzip, compress
    Accept-Language : en
    User-Agent : Lynx/2.8.3rel.1 libwww-FM/2.14FM
    
    
    headers in response:
    
    HTTP/1.0 200 OK
    Server: Resin/2.1.8
    Set-Cookie: JSESSIONID=aXOrOp6M9AQe; path=/
    Content-Type: text/html
    Content-Length: 0 
    Date: Mon, 06 Jun 2005 05:15:58 GMT
    
    注:有些程序/应用服务器针对head 请求做了相关处理,返回的 response header 与常规的get 请求可能不同,如上面response header 中的Content-Length 为0。

  5. ieHTTPHeaders
  6. ieHTTPHeaders 是Jonas Blunck设计的一款小工具,是专门用来查看request/response 中的header 信息的。

    headers in request:
    
    GET /examples/basic/env.jsp?foo=bar HTTP/1.1
    Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
    Accept-Language: zh-cn
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
    Host: dev.lhelper.org:8080
    Connection: Keep-Alive
    
    
    headers in response:
    
    HTTP/1.1 200 OK
    Server: Resin/2.1.8
    Cache-Control: private
    Set-Cookie: JSESSIONID=aLCtvHCEsQI5; path=/
    Content-Type: text/html
    Content-Length: 1542
    Date: Sun, 05 Jun 2005 23:59:25 GMT
    
    

  7. Web Developer - Firefox Extension
  8. headers in request:
    
    Host : dev.lhelper.org:8080
    User-Agent : Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4
    Accept : text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
    Accept-Language : en-us,en;q=0.7,zh-cn;q=0.3
    Accept-Encoding : gzip,deflate
    Accept-Charset : x-gbk,utf-8;q=0.7,*;q=0.7
    Keep-Alive : 300
    Connection : keep-alive
    Cookie : JSESSIONID=ao1frqfckQY7
    Pragma : no-cache
    Cache-Control : no-cache
    
    
    headers in response:
    
    Server: Resin/2.1.8
    Cache-Control: private
    Content-Type: text/html
    Content-Length: 1724
    Date: Mon, 06 Jun 2005 00:03:19 GMT
    200 OK
    
    
    注:Web Developer 插件总是试图获取服务器上的最新内容——他在请 求中加入了"Pragma : no-cache" 和 "Cache-Control : no-cache", 这样途中经过的缓存服务器将不直接对该请求进行响应,而是交给上 一层服务器进行处理,并用上一层服务器返回的内容更新本地的缓存。

  9. other tools

每个工具都有自己的特点,可以根据具体情况选用不同的工具(或综 合使用多个工具),发挥其作用。

Sunday, April 17, 2005

放慢脚步,我们不妨来等等时间!

一贯知足,二目远眺
三餐有节,四季不懒
五谷皆食,六欲不张
七分忍让,八方交往
酒薄烟断,十分坦荡
今天在香山又读到了这断文字。

Monday, March 28, 2005

让MT 的日历/月历链接到当日的归档文件

在MT(Movable Type 2.64) 的缺省模板中,日历/月历处链接的是当天的最后一篇日志,而 有时候我们希望它能链接到当天的归档文件上去。这时用 <$MTEntryLink archive_type="Daily"$> 替换缺省模板中的<$MTEntryPermalink$>就可以解决问题了, 如替换后的效果为:
<MTCalendar>
  <MTCalendarWeekHeader><tr></MTCalendarWeekHeader>
  <td align="center">
    <span class="calendar">
    <MTCalendarIfEntries><MTEntries lastn="1"><a href="<$MTEntryLink archive_type="Daily"$>"><$MTCalendarDay$></a></MTEntries></MTCalendarIfEntries>     <MTCalendarIfNoEntries><$MTCalendarDay$></MTCalendarIfNoEntries>
    <MTCalendarIfBlank>&nbsp;</MTCalendarIfBlank>
    </span>
  </td>
  <MTCalendarWeekFooter></tr></MTCalendarWeekFooter>
</MTCalendar>

参见:

Sunday, March 27, 2005

让MT 在日历/月历处显示上、下月的链接

在MT(Movable Type 2.64) 的缺省模板中日历/月历处并没有上、下月的链接,使用起来让 人稍闲不便,加入下面的代码就可以解决问题了:
<caption class="calendarhead">
<MTArchiveList archive_type="Monthly" lastn="1">
  <MTArchivePrevious archive_type="Monthly">
    <a href="<$MTArchiveLink archive_type="Monthly"$>"><$MTArchiveDate format="%b"$></a>
  </MTArchivePrevious>
</MTArchiveList>
<$MTDate format="%B %Y"$>
<MTArchiveList archive_type="Monthly" lastn="1">
  <MTArchiveNext archive_type="Monthly">
    <a href="<$MTArchiveLink archive_type="Monthly"$>"><$MTArchiveDate format="%b"$></a>
  </MTArchiveNext>
</MTArchiveList>
</caption>
注:用于显示下月归档的部分目前还没有测试过:*)
<MTArchiveList archive_type="Monthly" lastn="1">
  <MTArchiveNext archive_type="Monthly">
......

参见:

Saturday, March 26, 2005

让MT 显示所有分类(category)以及每个分类的日志数量

MT(Movable Type 2.64) 的缺省模板并没有列出作者所创建的分类, 以及每个分类中已发表的日志数量,这时用下面一段代码就可以解决问题 了:
<MTArchiveList archive_type="Category">
  <li><a href="<$MTArchiveLink$>"><$MTArchiveTitle$>
  (<$MTArchiveCount$>)</a></li>
</MTArchiveList>

参见:

Sunday, March 6, 2005

加强对HEAD 请求的处理

最近发现有些搜索引擎爬虫在抓取数据的时候,先是通过一个HEAD 请求获取response的header 信息,然后再通过GET 请求获取response 的body信息(即页面的内容)——先发送HEAD 请求是为了获得页面的更新时间(即response header 中的Last-Modified 域),用于判断自从上一次该页面被收入索引库以后有没有被更新过,如果判断页面没有被更新过就忽略该页面,否则就再用GET 方法获取一次最新的内容并更新到索引库中。

在页面更新频率比较低或者缓存设置的时间比较长的情况下,这样做可以避免在网络上传送体积比较大的body 域、降低网络消耗,而且还也可以缩短索引的更新时间。但在页面更新频率比较高,或者页面缓存时间比较短的情况下效果却是相反的:

如果被抓取的页面在缓存中,情况会稍微好一些,缓存服务器(如安装了expires_module 的Apache)在接收到HEAD 请求时会把缓存后的response 的header 域返回给爬虫,在接下来的GET 请求时再把缓存后的整个response (包括header 域和body 域)都返回给爬虫;

如果被抓取的页面不在缓存中,而程序中又缺少专门针对HEAD 请求的处理方法,那么就会导致该页面被生成两次——在处理HEAD请求的时候,因为没有专门的方法,于是一般用于处理GET 请求的方法就会被执行,程序执行后生成了完整的response,缓存服务器接收到该response,但只会把它的header 信息返回给爬虫,并不会对该response 进行缓存;在处理接下来的GET 请求的时候,因为没有缓存所以程序还要再生成一遍完整的response,并由缓存服务器转交给爬虫,这时缓存服务器才会把response 缓存起来。这样程序就被执行了两次,第一次执行很大程序上来说是一种浪费。

解决问题的一种方法就是在程序中加入对HEAD 请求的处理。在处理HEAD 请求的时候一般只要设置response header 中Content-Type 和Content-Length 就可以了,如: 在servlet 可以通过重载doHead(HttpServletRequest request, HttpServletResponse response) 的方法实现:

public void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  // Set the content length and type
  resp.setContentType("text/html; charset=GB2312");
  resp.setContentLength(30000);
}

而在jsp 中则可以仿照下面的方式:

<%
/* handle the HEAD request */
if(request.getMethod().equals("HEAD")) {
  response.setDateHeader("Last-Modified", System.currentTimeMillis());  /* 设置Last-Modified */
  response.setContentType("text/html; charset=GB2312"); /* 设置Content-Type */
  response.setContentLength(30000);  /* 设置 Content-Length */
  return;
}
%>

下面是log 中的一个片断,显示了IP为202.108.1.4 的某个用户/爬 虫/代理服务器(奇怪的UserAgent 项)的访问日志:
202.108.1.4 - - [06/Mar/2005:11:21:03 +0800] "HEAD /2001-03-07/28456.htm HTTP/1.1" 200 0 "-" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"
202.108.1.4 - - [06/Mar/2005:11:21:03 +0800] "GET /2001-03-07/28456.htm HTTP/1.1" 200 32182 "-" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"
202.108.1.4 - - [06/Mar/2005:11:21:09 +0800] "HEAD /2003-06-26/169417.htm HTTP/1.1" 200 0 "-" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"
202.108.1.4 - - [06/Mar/2005:11:21:09 +0800] "GET /2003-06-26/169417.htm HTTP/1.1" 200 34693 "-" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"
202.108.1.4 - - [06/Mar/2005:11:21:11 +0800] "HEAD /2005-1-5/361944.htm HTTP/1.1" 200 0 "-" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"
202.108.1.4 - - [06/Mar/2005:11:21:11 +0800] "GET /2005-1-5/361944.htm HTTP/1.1" 200 36761 "-" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"

另,目前只有较少的老式搜索引擎爬虫在采用这种方式抓取页面,如AOL 的爬虫,而大部分搜索引擎爬虫都在采用另外一种方式:即在GET 请求的header 中加入If-Modified-Since 项,交由服务器判断页面是否被更新过。

参见:

  1. All About Search Indexing Robots and Spiders
  2. http://www.searchtools.com/robots/
  3. Stealth bots. How to detect them?
  4. http://www.webmasterworld.com/forum11/2562.htm
  5. 超文本传输协议 -- HTTP/1.0 (Hyptertext Transfer Protocol - HTTP/1.0)
  6. http://www.delphidevelopers.com/technical/RFC/RFCs/RFC1945.txt

Tuesday, March 1, 2005

用正则表达式剔除文本中的 html 标记

为了剔除文本中的html 标记,通常大家都会使用下面的正则表达式:

/<.*?>/s

在大部分情况下该表达式都能正常运行,但在某些情况下却会出现差 错,例如当html 标记含有带 '>' 符号的script 脚本时,如:

<img src='images/1109669135478.jpeg' onload='if(this.width>screen.width-500){this.width=screen.width-500;}' border=0>

此时上面的正则表达式只能匹配到

<img src='images/1109669135478.jpeg' onload='if(this.width>

而无法匹配整个img 标记,这时综合使用下面的几个正则表达式就可以解决 问题了:

  1. /<!--.*?-->/s
  2. /<([^>]*?('.*?'|".*?")[^>]*?)*?>/s
  3. /<.*?>/s
说明:
  1. 首先剔除注释标记,这样可以同时剔除掉注释中可能包含的其他html 标记
  2. 剔除含属性声明的html 标记,这样可以剔除上面所提到的在属 性中出现的'>'
  3. 剔除不含属性声明的html 标记
注:上面的正则表达式在遇到特殊javascript 代码时可能会出现问 题:*(

Sunday, February 27, 2005

今天去了潭柘寺

很早就想出去活动一下了,但由于周末只有一天的活动时间,又总是 被各种各样的事情占去一部分,所以很久都没有出去过了:-(,这个 周末总算是能付诸行动了:)——去了一趟潭柘寺!

潭柘寺位于北京西郊,属门头沟区,距离苹果园地铁站约有30多公里 的路程(按公交车的行程计算)。公交车还算方便——931路起点苹 果园,终点就是潭柘寺——只是931路的卫生条件稍微差一些,车里 面显得脏嘻嘻的。

早上我大概7:20从家里出来,7:50 到四惠换乘地铁,8:50 到达苹果园, 在苹果园附近找931 花了差不多20多分钟:-(——主要是被931 (支)和931(专)迷惑了一下——931(支)和931(专) 位于苹果园地铁出 口东边不远处,但两趟车都不到潭柘寺;而931 则位于地铁出口西边约 300米处,并且被夹杂在很多小广告和黑车中间很难辨认。931 在 路上大概走了一个半小时的时间——因为要绕很远的一段路。这样上 午花在路上的时间就约有3个半小时。

据记载,潭柘寺在鼎盛时期仅记名的和尚就有3000多,不计名的和尚 则不计其数——号称当时寺中僧侣每天消耗的粮食相当于一个普通村庄 一年所产粮食的总和(幸亏当时潭柘寺周边大大小小的360多个村 子在向潭柘寺供奉粮食)。也许是日子的关系,今天在潭柘寺中只看 到了很少的和尚,三三两两的应该没有超过10个;但到寺中烧香的 信徒还算是比较多的。

潭柘寺中供奉了观世音菩萨,文殊菩萨和地葬王菩萨,但好像没有普 贤菩萨。供奉观世音菩萨的地方最多,除了中路好像有两个大殿都是 以外,东、西两路还分别有一个观音洞。

......

潭柘寺里几乎到处都是典故和故事,每一件东西都有他的来历和传说, 有些是真实的,有些则是经过加工和夸大的。象山门前的盘龙松、 大雄宝殿前的帝王树、配王树和婆罗树,还有象百(柏)事如(榕) 意、石鱼等等等等。即便你不是佛教信徒,你也可以到那里去搜寻一 些美丽的传说。

Wednesday, February 23, 2005

Advanced Text Indexing with Lucene by Otis Gospodnetic

Advanced Text Indexing with Lucene by Otis Gospodnetic -- Lucene is a free text-indexing and -searching API written in Java. In the second of a series of articles, Otis Gospodnetic explains the structure of Lucene indices and introduces several advanced techniques to improve the performance of text-indexing applications.

今天在O'Reilly 的 OnJava.com 上发现了Otis Gospodnetic 的关于Lucene 的一篇文章,文章讲到了如何通过IndexWriter 的 mergeFactor 和 maxMergeDocs 参数来提高索引创建过程的效率和控制 索引的segment 数量以及IndexWrite的 optimize()方法如何影响 segment。看样子是可以通过协调以上几个参数和方法来控制单个索 引文件的大小使之小于1GB 的。

Monday, January 24, 2005

"Pager Tag Library"-一个非常不错的jsp 分页控件

前段时间有编辑上了一条比较敏感的新闻,由于事先没有做好对评论部 分的控制与过滤,被国安部发现了,结果封了几个站点,其余的都被要 求限期更改。所以几天以来全部都是在忙着屏蔽关键字、删除 敏感信息...几天时间就这样过去了:-(

今天发现了一个基于jsp 的分页控件"Pager Tag Library",出自jsptags.com (James Klicman),这里是他的Demo

The Pager Tag Library is the easy and flexible way to implement paging of large data sets in JavaServer Pages (JSP). It can emulate all currently known paging styles with minimal effort. It also includes re-usable index styles that emulate the search result navigators of popular web sites such as GoogleSM, AltaVista® and Yahoo!. The Pager Tag Library does most of the work for you by dynamically organizing your data set into pages and generating a browsable index with virtually any look desired.

google 上可以了解到有很多站点都在用它来显示分页。

Monday, January 3, 2005

恢复了blog 的 rss

前段时间屏蔽掉了blog 的rss,当时主要是由于rss 文件的encoding 有问题,总是出现乱码,而又苦于没时间去改正,所以就暂时把它屏 蔽掉了:-(

后来车东一再催促,于是 才开始断断续续的寻找解决问题的方法。今天总算是把它给恢复回来 了——在解决问题的过程当中发现BlogMax 的设计总体来说是相当不 错的,值得花一段时间去研究一下。

下面把问题的解决过程、以及对BlogMax 的改动简单记录一下:

  1. 为rss 模板添加encoding属性
  2. 为rss-template.xml 加入encoding属性,如下:
    <?xml version="1.0" encoding="gb2312"?>
    <rss version="0.91">

  3. 修改blogmax.el 中的weblog-make-rss (&optional text-file) 方法
  4. Bill St. Clair 最初在 设计BlogMax 的时候,把每篇日志中<p> 与 </p>之间 的部分看作是一个完整的story (或者称为是entry)——这对于我来 说简直是一场灾难——我更倾向于用<story></story> (<entry></entry>)来表示:-)
    修改方法:
    1.用entry 替换day-template.tmpl 中的p 标记,如下:
    <p>
    『storyContent』
    </p>
    <entry>
    『storyContent』
    </entry>
    2.修改blogmax.el 中的weblog-make-rss (&optional text-file) 方法,如下:
    (goto-char (point-min))
      (loop
        (let* ((start (point))
          (end (search-forward "<p>" nil t))
          (real-end (if end (- end 3) (point-max)))
    
    (goto-char (point-min))
      (loop
        (let* ((start (point))
          (end (search-forward "<entry>" nil t))
          (real-end (if end (- end 3) (point-max)))
    

附:
blogmax.el 中有很多处没有完全看懂,以后有必要学习一下;
有时间的话和Bill St. Clair沟通一下,BlogMax 有些地方是值得改 进的;

Tuesday, December 28, 2004

收到一个网友的来信“关于不同域之间的数据交换”

下班的时候收到网友chenhuibing 的一封来信,是关于我以前的一篇文 章“实现不同域(Domain)之间的数据交换”的,该网友对文中的部分内容提出了疑义。信的内容如下:

From :  chenhuibing <此处隐去了发信人的邮件地址> 
Sent :  Tuesday, December 28, 2004 5:58 PM 
To :  lvkrnewer@hotmail.com 
Subject :  关于不同域之间的数据交换 
 
hi:吕克
 
  我发现通过main.htm中打开不同域的remotedialog.html, 在main.htm中不能取得返回的结果
 
而你 说::
main.html 总是能正常的接收从对话框中返回的结果,无论对话框是位于a.mycompany.com 还是 b.mycompany.com,也无论是否设置了 document.domain 属性;
 
请问你是如何取得结果的?  
 
Best regards
-----------------------------
此处隐去了该网友的姓名及联系方式
OSS
----------------------------- 

文章是我在2004年5、6月份写的,8月份的时候post 到了csdn,后来 被自动收录到csdn 的blog 里面。我曾经在文中写道“main.html 总是能正常的接收从对话框中返回的结 果,无论对话框是位于a.mycompany.com 还是 b.mycompany.com,也 无论是否设置了 document.domain 属性;”,即无论对话 框是否隶属于相同的域,当前页面(main.htm)总是能接收到它所返回的值。

网友指出当对话框隶属于另外一个域的时候,main.htm 是无法取得 它的返回值的

首先给这位网友回了邮件,然后开始在本地重新进行验证。下面是我 的测试环境:
操作系统: Microsoft Windows 2000 [Version 5.00.2195](简体中 文版)
浏览器:Microsoft Internet Explorer Version 6.0.2800.1106(简体中 文版)
Web服务器:Server version: Apache/1.3.33 (Win32)
(前两者都打了最新的补丁程序)

验证结果和那位网友说得是一样的,当对话框和mail.htm 隶属于不 同域的时候,main.htm 是无法得到对话框的返回值的 :-(

???是我当初在写文章的时候弄错了(我当初确实是认真测试过了呀)? 还是后来ms 的ie 补丁禁止了某些跨域的数据交换操作(要知道ms 经常在给自己的东西打补丁呀)?先去找个没 打补丁的裸系统试一下,争取找到原因,争取还自己一个清白:-P

刚刚找到的相关资料:

更多资料: http://www.google.com/search?hl=zh-CN&newwindow=1&q=ie+showModalDialog+domain

Thursday, December 23, 2004

认识了 blog.o2.cn 的作者

昨天晚上MSN 上的一个朋友(偶尔认识的,并且很久没有互通过消息,)问我是否还在研究 OpenCms、是否试用过他的 6.0 beta 版,回答已经很久不在做真正的研究了,并且建议他也不 要再花费太多的时间在上面——原因也许以后我会总结:-)

认识这个朋友也算是偶然,当初是由于我在其他地方post 过一些关 于OpenCms 的文字,并且吹嘘自己正在做这方面的研究 :-P;而他 当时正在为自己的站点挑选CMS,恰巧看到了我写的那些东西,于 是就找到了我。

那位朋友向我推荐了他的Blog -老公 的blog (http://blog.o2.cn),以及一个他正在建设、很快就可 以面世的关于LAMP、网站建设和开源项目的网站cnwebservices.com

说来真的是很巧,昨天下午的时候我用google 查找资料,从搜索结果 中就链接到了他的站点,偏偏当时不能访问,所以印象比较深。后来从他那 里得到了证实——当时他正在做更新,导致blog 中断了一会——偏 偏就被我遇到了:-)

后来两个人谈论起了CMS,他向我介绍他的blog.o2.cn 是基于ez Publish的,下面是他的关于CMS选型 与eZ publish 的文章。

--个人观点:

CMS 因人而异,没有一款CMS 可以满足所有人,就比如现在我所用的Blogmax,尽管他的功能很少,目前国内使 用他的人不超过20个(根据google 的统计结果),但他对于我来说已经是够用得了:-)

开源的系统--应用他并且去适应他,不要让他来适应你——除非你是他的developer 的一分子

Monday, December 20, 2004

Find Your Favourite Desktop Theme

你对windows 的操作界面感到厌倦了吗?看看这里吧:-)

Holiday Desktop
刚才在搜索google skins 的时候,偶然发现了这个站点,里面有很多漂亮的windows 主题,由于时间关系没有仔细研究,不知道有没有license 限制,如果有兴趣的话就去看看吧:-)

WinCustomize
http://www.wincustomize.com/

Stardock Home
http://www.stardock.com/

Friday, December 17, 2004

用 truncate table 清空表

truncate table your_table_to_be_empty
delete from your_table_to_be_empty
若要删除表中的所有行,则 TRUNCATE TABLE 语句是一种快速、无日志记录的方法。该语句总是比不带条件的 DELETE 语句要快,因为 DELETE 语句要记录对每行的删除操作,而 TRUNCATE TABLE 语句只记录整个数据页的释放。TRUNCATE TABLE 语句立即释放由该表的数据和索引占用的所有空间。所有索引的分发页也将释放。
与 DELETE 语句相同,使用 TRUNCATE TABLE 清空的表的定义,同其索引和其它相关的对象一起仍保留在数据库中。必须使用 DROP TABLE 语句才能除去表的定义。

Saturday, October 30, 2004

在XSLT 中用translate() 实现大小写转换

Lowercase/Uppercase stirng in xslt use translate().

将小写字符转换成大写字符:
translate($word, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')

与之相对应的
translate($word, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
可以将大写字符转换成小写字符。

translate(): string=translate(value,string1,string2)
Performs a character by character replacement. It looks in the value argument for characters contained in string1, and replaces each character for the one in the same position in the string2

参见:
XPath Functions(http://www.w3schools.com/xpath/xpath_functions.asp)

附:第一次看到translate() 还是在大四实习期间学习oracle 的时候, 当时是有人在用他实现简单的加密/解密,例如:
translate($word, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZa")
将会把$word 中出现的'a' 替换成'b','b'替换成 'c',...,'Y' 替换成'Z','Z'替换成'a',这样就相当于对$word 进行了加密;

translate($word, "bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZa", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
是前面的translate() 的逆过程,相对于是对密文进行了解密。

Wednesday, October 27, 2004

在redhat linux 上编译resin 经常遇到的问题

先简单记录一下,将来有时间再详细解释:-)

相关配置:
Redhat Linux 2.4.20-31.9smp
OpenSSL 0.9.7a
Java HotSpot(TM) Client VM (build 1.4.2_03-b02, mixed mode)
Resin 2.1.14

缺省情况下进行编译一般会遇到下面的错误:

gcc -g -O2 -DRESIN_HOME="$your_resin_home/resin-version" -I -I/usr/kerberos/include -I/usr/java/jdk/include -I/usr/java/jdk/include/linux -I../common -DCPU="i386" -DOS=    -c -o ssl.o ssl.c
In file included from /usr/include/openssl/ssl.h:179,
                 from ssl.c:62:
/usr/include/openssl/kssl.h:72:18: krb5.h: No such file or directory
In file included from /usr/include/openssl/ssl.h:179,
                 from ssl.c:62:
/usr/include/openssl/kssl.h:132: parse error before "krb5_enctype"
/usr/include/openssl/kssl.h:134: parse error before "FAR"
/usr/include/openssl/kssl.h:135: parse error before '}' token
/usr/include/openssl/kssl.h:147: parse error before "kssl_ctx_setstring"
/usr/include/openssl/kssl.h:147: parse error before '*' token
/usr/include/openssl/kssl.h:148: parse error before '*' token
/usr/include/openssl/kssl.h:149: parse error before '*' token
/usr/include/openssl/kssl.h:149: parse error before '*' token
/usr/include/openssl/kssl.h:150: parse error before '*' token
/usr/include/openssl/kssl.h:151: parse error before "kssl_ctx_setprinc"
/usr/include/openssl/kssl.h:151: parse error before '*' token
/usr/include/openssl/kssl.h:153: parse error before "kssl_cget_tkt"
/usr/include/openssl/kssl.h:153: parse error before '*' token
/usr/include/openssl/kssl.h:155: parse error before "kssl_sget_tkt"
/usr/include/openssl/kssl.h:155: parse error before '*' token
/usr/include/openssl/kssl.h:157: parse error before "kssl_ctx_setkey"
/usr/include/openssl/kssl.h:157: parse error before '*' token
/usr/include/openssl/kssl.h:159: parse error before "context"
/usr/include/openssl/kssl.h:160: parse error before "kssl_build_principal_2"
/usr/include/openssl/kssl.h:160: parse error before "context"
/usr/include/openssl/kssl.h:163: parse error before "kssl_validate_times"
/usr/include/openssl/kssl.h:163: parse error before "atime"
/usr/include/openssl/kssl.h:165: parse error before "kssl_check_authent"
/usr/include/openssl/kssl.h:165: parse error before '*' token
/usr/include/openssl/kssl.h:167: parse error before "enctype"
In file included from ssl.c:62:
/usr/include/openssl/ssl.h:909: parse error before "KSSL_CTX"
/usr/include/openssl/ssl.h:931: parse error before '}' token
make[2]: *** [ssl.o] Error 1

这时必须要设置编译选项CPPFLAGS 才能够解决问题,方法是:
$ CPPFLAGS=-I/usr/kerberos/include
$ export CPPFLAGS
$ make
# make install

参考资料:

Tuesday, October 26, 2004

Blogging in Emacs with bolgmax

  1. #blogging in Emacs with bolgmax
  2. 开始用Emacs + blogmax 记录日常工作、日常生活中的所学所思

  3. #jde-jalopy: jalopy for Emacs
  4. 在Emacs 中用jde-jalopy 格式化java 代码

Tuesday, June 29, 2004

WebLucene 的更新备忘(2004-06-29)

昨天,也就是2002-06-29,把对weblucene 的修改commit 到sourceforge.net上去了。sourceforge.net 的cvs 操作起来 要稍微麻烦一些,必须先通过ssh 登录一次 cvs.sourceforge.net,并把CVS_RSH 设置为ssh, 然后才能够 通过ext 进行更新和提交,并且我得每次add、commit 操作都要 数一次密码:(
$ export CVS_RSH=ssh
$ ssh -l lhelper cvs.sourceforge.net
$ cvs -d:ext:lhelper@cvs.sourceforge.net:/cvsroot/weblucene
详见:http://sourceforge.net/projects/weblucene/

此次更新的主要内容:
  • 重构com.chedong.weblucene.search.WebLuceneQuery,使之看 起来更像是一个javabean:通过getter、setter 来控制其private field;
  • 用com.chedong.util.ParamUtil 和 com.chedong.util.RequestParser 替换 com.chedong.weblucene.Validator,简化了参数验证部分的代码;
  • 采用新的配置管理机制:即用com.chedong.properties 包中的 类替换掉com.chedong.weblucene.ConfigManager、 com.chedong.weblucene.WebLuceneAppConfig 和 com.chedong.weblucene.WebLuceneConfig。虽然 WebLuceneAppConfig 得到了保留,但他和WebLuceneGlobalConfig 一起都只是作为 com.chedong.properties.SimplePropertiesConsumer 的子类,类似 于utility。
    有哪些配置文件需要交由新的配置管理机制来管理需要在 web.xml 进行设置,如:
      <servlet-name>admin</servlet-name>
      <servlet-class>com.chedong.weblucene.WebLuceneAdminServlet</servlet-class>
      <init-param>
      <param-name>properties.path</param-name>
      <param-value>WEB-INF/conf</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
      <servlet-name>admin</servlet-name>
      <url-pattern>/admin</url-pattern>
      </servlet-mapping>
    上面的设置意指把{$WEBLUCENE_WEBAPP}/WEB-INF/conf 目录下的 *.conf 和 *.properties plain property文件交给配置管理器进行 管理;注意配置文件应该以".conf" 或".properties" 作为扩展名。
  • 配合新的配置管理机制,把 {$WEBLUCENE_WEBAPP}/WEB-INF/var/*/wl.conf 转移到 {$WEBLUCENE_WEBAPP}/WEB-INF/conf/$1.conf,并在weblucene.conf 中加入部分默认属性。
  • 改进对Exception 的处理方式:仿照对正常结果的处理,用xml + xslt 封装Exception,即当遇到Exception时仍然按照 OutputFormat 输出html 或者rss、xml 形式的结果。这里的 Exception 目前指涉及到两种——Null keywords 和 Parser Exception。
  • 配合对Exception 的改进,在 {$WEBLUCENE_WEBAPP}/WEB-INF/var/blog/ 下加入html_null.xsl 和 html_err.xsl。

xml


Created with: Emacs, BlogMax source