解決jive搜尋結果中的中文搜尋字串高亮度顯示的方法

zhuojun發表於2003-07-25
1.問題:在搜尋結果中,命中的中文字串不能正確地高亮度顯示。
2.工作過程:Jive用搜尋字串生成一個正規表示式,然後在搜尋得到的結果文字中用該表示式做替換操作,
如果匹配成功,就進行替換操作。不能匹配,則不進行替換操作。替換操作的結果是將Html格式語句和搜尋
字串插入到結果文字中,顯示時候就可以看見高亮度的字串。
jive中的jakarta-oro-XXX包中含有相容Perl5正規表示式的類,jive使用其substitute()方法實現替換操作。
3.原因:jive生成如下的Perl5錨模式的正規表示式:
  s/\b(XXX)\b/<font style='background-color:ffff00'><b>$1<\/b><\/font>/igm
  XXX表示匹配字串。上式含意看看正規表示式的資料就明白了。
  問題出在模式部分"\b(XXX)\b"。這樣的匹配是要求首尾做邊界匹配(單詞匹配),中文書寫格式中,邊界一
  般是開始的空格和句中及結尾的標點符號,所以完成匹配很困難。如:
  月落烏啼霜滿天,江楓漁火對愁眠。
  只有”\b(月落烏啼霜滿天)\b”和“\b(江楓漁火對愁眠)\b”兩個匹配是正確的,諸如”\b(月落)\b”、”\b(烏啼)\b”、
  ”\b(滿天)\b”等都無法匹配。因此也就不能完成替換操作。只有搜尋字串正好被分隔符包圍,才能完成替換操作。
  所以實際使用中經常是有時中文可以高亮度顯示,而大部分則不行。就是因為一般的查詢很難滿足上述條件。
3.修改方法:
  (1)分析此處的匹配要求是:中文任意位置匹配,英文單詞匹配。
     要求寫出這樣的正規表示式挺難(不能?),如有熟悉正規表示式的請指正。
  (2)最簡單的方法是去掉匹配的引數b,做任意匹配處理。即使用這樣的正規表示式:
     s/(XXX)/<font style='background-color:ffff00'><b>$1<\/b><\/font>/igm
     找到com.jivesoftware.util.StringUtils.java檔案中的highlightWords()方法的下列語句,修改為註釋中的語句。
     regexp.insert(0, "s/\\b(");  // 修改此句為:regexp.insert(0, "s/(");
     // The word list is here already, so just append the rest.
     regexp.append(")\\b/");      // 修改此句為regexp.append(")/");
     regexp.append(startHighlight);
     regexp.append("$1");
     regexp.append(endHighlight);
     regexp.append("/igm");
     這樣修改最方便,對中文效果不錯,但是對英文會出現單詞中匹配,如:
     give me a pen or pencil.
     搜尋“pen”時,pen和pencil中的前三個字母都會作為高亮度顯示出來,後一種情況看起來很彆扭。
  (2)如果做的考究一點,可以將中文和英文分開處理。使用這樣的正規表示式:
     s/(XXX|\bYYY\b)/<font style='background-color:ffff00'><b>$1$2<\/b><\/font>/igm
     XXX表示中文匹配字串,YYY表示英文匹配字串。修改的程式如下:
  /**
   * Highlights words in a string. Words matching ignores case. The actual
   * higlighting method is specified with the start and end higlight tags.
   * Those might be beginning and ending HTML bold tags, or anything else.<p>
   *
   * This method is under the Jive Open Source Software License and was
   * written by Mark Imbriaco.
   *
   * @param string the String to highlight words in.
   * @param words an array of words that should be highlighted in the string.
   * @param startHighlight the tag that should be inserted to start highlighting.
   * @param endHighlight the tag that should be inserted to end highlighting.
   * @return a new String with the specified words highlighted.
   */
  /**
   * 一個匹配"中文" 和 “english"的正規表示式:
   * 英文做單詞匹配(單詞首尾邊界匹配),中文任意匹配。
   * s/(中文|\benglish\b)/<font style='background-color:ffff00'><b>$1$2<\/b><\/font>/igm
   * 選項說明
   * i 忽略模式中的大小寫
   * g 改變模式中的所有匹配
   * m 將待匹配串視為多行
   *
   * 2003-6-22
   */

  public static final String highlightWords(String string, String[] words,
					    String startHighlight,
					    String endHighlight) {
    if (string == null || words == null ||
	startHighlight == null || endHighlight == null) {
      return null;
    }

    StringBuffer regexp = new StringBuffer();

    // Iterate through each word and generate a word list for the regexp.
    for (int x = 0; x < words.length; x++) {
      // Excape "|" and "/"  to keep us out of trouble in our regexp.
      words[x] = perl5Util.substitute("s#([\\|\\/\\.])#\\\\$1$2g", words[x]);
      if (regexp.length() > 0) {
	regexp.append("|");
      }
      if (words[x].charAt(0) > 127) {
// 中文不做邊界匹配
	regexp.append(words[x]);
      }
      else {
// 英文單詞做邊界匹配檢查
	try {
	  PatternCompiler compiler = new Perl5Compiler();
	  String regex = "\\b" + words[x].toString() + "\\b";
	  Pattern pattern = compiler.compile(regex);
	  PatternMatcher matcher = new Perl5Matcher();
	  if (matcher.contains(string, pattern)) {
// 如果有邊界匹配的單詞,正規表示式使用邊界匹配
	    regexp.append(regex.toString());
	  }
	  else {
// 使用任意匹配
	    regexp.append(words[x]);
	  }
	}
	catch (Exception e) {
	  System.out.println(e);
	}
      }
    }

    // Escape the regular expression delimiter ("/").
    startHighlight = perl5Util.substitute("s#\\/#\\\\/g", startHighlight);
    endHighlight = perl5Util.substitute("s#\\/#\\\\/g", endHighlight);

    // Build the regular expression. insert() the first part.
    regexp.insert(0, "s/(");
    // The word list is here already, so just append the rest.
    regexp.append(")/");
    regexp.append(startHighlight);
    regexp.append("$1$2");
    regexp.append(endHighlight);
    regexp.append("/igm");

    // Do the actual substitution via a simple regular expression.
    return perl5Util.substitute(regexp.toString(), string);
  }
  
4.給搜尋結果的標題(subject)部分加上高亮度顯示
  在search.jsp檔案中找到有highlightWords()方法的一行,在前面加入兩句
  String subject = StringUtils.escapeHTMLTags(message.getUnfilteredSubject());
  subject = StringUtils.highlightWords(subject, queryWords, "<font style='background-color:ffff00'><b>", "</b></font>");
  後面將subject給瀏覽器就行了。參見下面程式碼的註釋。
  ......
  while (results.hasNext()) {
    ForumMessage message = (ForumMessage)results.next();
// 新增的語句。subject中命中字串的高亮顯示
    String subject = StringUtils.escapeHTMLTags(message.getUnfilteredSubject());
    subject = StringUtils.highlightWords(subject, queryWords, "<font style='background-color:ffff00'><b>", "</b></font>");
//
    String body = StringUtils.escapeHTMLTags(message.getUnfilteredBody());
    body = StringUtils.chopAtWord(body, 150) + " ...";
    body = StringUtils.highlightWords(body, queryWords, "<font style='background-color:ffff00'><b>", "</b></font>");
    long mID = message.getID();
......
   <td>
   <font class=p2 face="<%= JiveGlobals.getJiveProperty("skin.default.fontFace") %>">
   <a href="thread.jsp?forum=<%= fID %>&thread=<%= tID %>&message=<%= mID %>&redirect=true&hilite=true&q=<%= StringUtils.ISOtoGBK(queryText) %>"
<%-- 將message.getSubject()改為subject。subject高亮度顯示  --%>
<%-- ><%= message.getSubject() %></a> --%>
   ><%= subject %></a>
......
   <%  } // end while loop
   %>


參見正規表示式的有關資料。
<p class="indent">

相關文章