全文检索高亮工具类

2 篇文章 0 订阅
1 篇文章 0 订阅

全文检索高亮工具类

—— Ivan / iu软件(www.iusofts.com)

  • 利用pinyin4j将汉字转换为拼音
  • 检索内容高亮(支持关键词高亮、被截取后的关键词高亮、同音关键词高亮)

1.高亮主工具类

package com.iusoft.sys.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

/**
 * 高亮工具类
 * 
 * @author Ivan
 * @version 2.1
 * 
 * 功能描述:
 *        1.普通高亮 
 *        2.同音高亮 
 *        3.汉字拼音混合高亮
 *        4.自动合并
 *        5.智能截取
 */
public class HighlightUtil {

    public static void main(String[] args) {
        // String content = "2015年被市场称为国企改革的“落地年”。市场时常会失控,所以要时常把握市场。";
        String content="姓名:张三  拼音:zhangsan";
        String keyword[]="zhangsan".split(" ");
        //String content = "市场时常会失控,所以要时常把握市场。";
        //String keyword[] = "市场 suoyi".split(" ");
        System.out.println(contentHighlight(content, keyword, true, 0));
    }

    /**
     * 检索内容高亮
     * 
     * @param content
     *            内容
     * @param keywords
     *            搜索关键词
     * @param isHomophony
     *            是否同音
     * @param maxLength
     *            最大长度(0为不限制)
     * @return
     */
    public static String contentHighlight(String content, String keywords[],
            boolean isHomophony, int maxLength) {
        String cutContent = "";// 参照未截取内容
        boolean isCut = false;
        // 1.根据最大长度截取内容
        if (maxLength > 0) {
            if (StringUtils.isNotBlank(content) && content.length() > maxLength) {
                cutContent = content.substring(0, maxLength);
                isCut = true;
            }
        }
        try {
            List<Postion> list = new ArrayList<Postion>();// 所有匹配下标位置的集合
            // 2.遍历关键词-匹配所有关键词下标位置
            for (int i = 0; i < keywords.length; i++) {
                if (StringUtils.isNotBlank(keywords[i].trim())) {
                    list.addAll(getAllIndex(content, keywords[i], isHomophony));
                }
            }
            // 3.下标集合-并集处理
            if (isCut) {// 是否被截取
                //如果文本内容已被截取,更新关键词在文本中正确的结束下标位置
                for (int i = 0; i < list.size();) {
                    if (list.get(i).start > maxLength - 1) {//如果关键词的开始位置大于截取长度
                        list.remove(i);//移除无效下标位置
                    } else {
                        //如果关键词开始下标在截取范围内,结束下标在截取范围外,将结束下标改为文本最大下标
                        if (list.get(i).end > maxLength - 1)
                            list.get(i).end = maxLength - 1;
                        i++;
                    }
                }
            }
            // 3.1 合并下标位置
            for (int i = 0; i < list.size(); i++) {
                for (int j = i + 1; j < list.size(); j++) {
                    list.get(i).union(list.get(j));
                }
            }
            // 3.2 去除被合并的下标位置
            for (int i = 0; i < list.size();) {
                if (list.get(i).isUnion()) {
                    list.remove(i);
                } else {
                    i++;
                }
            }
            // 4.替换文本标签-关键词高亮
            // 4.1 按下标开始位置从小到大排序
            Collections.sort(list, new Comparator<Postion>() {
                public int compare(Postion arg0, Postion arg1) {
                    return arg0.start - arg1.start;
                }
            });

            // 4.2给文本中关键词添加高亮标红的闭合标签
            String font = "<font color='red'>";
            String font2 = "</font>";
            if (isCut)
                content = cutContent;
            for (int i = 0; i < list.size(); i++) {
                int be = i * (font.length() + font2.length());// 被标签占用的位置
                content = content.substring(0, be + list.get(i).start)
                        + font
                        + content.substring(be + list.get(i).start,
                                be + list.get(i).end + 1)
                        + font2
                        + content.substring(be + list.get(i).end + 1,
                                content.length());
            }
        } catch (Exception e) {
            System.out.println("高亮处理错误!");
            e.printStackTrace();
            if(isCut)return cutContent+"...";
        }
        return isCut?content+"...":content;
    }

    /**
     * 获取关键词匹配下标
     * 
     * @author:Ivan
     * @date:2015年11月3日 上午10:53:31
     * @param content
     * @param str
     * @param isHomophony
     * @return
     */
    public static List<Postion> getAllIndex (String content, String str,
            boolean isHomophony) throws Exception{
        String cs[] = content.split("");
        String strs[] = { "", str.toUpperCase() };
        if (isChinese(str)) {
            strs = str.split("");
        }

        // 所有匹配的下标位置集合
        List<Postion> list = new ArrayList<Postion>();
        Postion point = null; // 用来存储匹配的开始下标、结束下标
        if (isHomophony) {// 如果同音,将每一项转换成拼音
            // 将内容转换成拼音
            for (int i = 1; i < cs.length; i++) {
                if (StringUtils.isNotBlank(cs[i].trim())) {
                    cs[i] = PinyinTool.toPinYin(cs[i]);
                }
            }
            // 将关键词转成拼音
            for (int i = 1; i < strs.length; i++) {
                if (StringUtils.isNotBlank(strs[i].trim())) {
                    strs[i] = PinyinTool.toPinYin(strs[i]);
                }
            }

            if (isChinese(str)) {// 纯汉字匹配
                for (int i = 1; i < cs.length; i++) {

                    int count = 0;// 成果匹配次数
                    if (i + strs.length - 1 > cs.length)
                        break;// 剩余匹配个数不足时跳出
                    for (int j = 1; j < strs.length; j++) {
                        if (cs[i + j - 1].equals(strs[j]))
                            count++;
                    }
                    // 当关键词的每一项都匹配时添加匹配下标
                    if (count == strs.length - 1) {
                        point = new Postion(i - 1, i - 1 + strs.length - 2);
                        list.add(point);
                    }
                }
                //获取拼音关键词在文本中所有下标位置
                String index = getAllIndex(content.toUpperCase(), PinyinTool.toPinYin(str), 0);
                if(StringUtils.isNotBlank(index)){
                    // System.out.println(index);
                    String indexs[] = index.split(",");
                    for (int i = 0; i < indexs.length; i++) {
                        int n = Integer.parseInt(indexs[i]);//开始下标位置
                        point = new Postion(n, n+PinyinTool.toPinYin(str).length() - 1);
                        list.add(point);
                    }
                }
            } else {// 混合匹配
                //获取拼音关键词在文本中所有下标位置
                String index = getAllIndex(PinyinTool.toPinYin(content.toUpperCase()), strs[1], 0);
                if(StringUtils.isNotBlank(index)){
                    // System.out.println(index);
                    String indexs[] = index.split(",");

                    //处理逻辑:
                    //  如果拼音关键词开始位置是文本内容中某个拼音的开始位置,并且拼音关键词的结束位置是文本内容中某个拼音的结束位置,
                    //视为有效关键词,并记录该拼音关键词的开始下标和结束下标。
                    int count = 0;//成功匹配次数,两次为有效匹配,否则为无效
                    for (int i = 0; i < indexs.length; i++) {
                        int n = Integer.parseInt(indexs[i]);//开始下标位置
                        int sum = 0;//下标累计
                        int start = 0;//开始下标
                        int end = 0;//结束下标
                        for (int j = 1; j < cs.length; j++) {
                            if (n == sum && count == 0) {//满足开始位置,成功匹配一次
                                count = 1;
                                start = j;
                            }
                            sum += cs[j].length();//累加文本下标位置
                            if (n + strs[1].length() == sum && count == 1) {//满足结束位置,成功匹配两次
                                count = 2;
                                end = j;
                                break;
                            }
                        }
                        if (count == 2) {//成功匹配两次,保存有效起始下标位置
                            point = new Postion(start - 1, end - 1);
                            list.add(point);
                        }
                        count = 0;//初始化匹配次数
                    }
                }
            }
        }else{
            //获取拼音关键词在文本中所有下标位置
            String index = getAllIndex(content.toUpperCase(), str, 0);
            if(StringUtils.isNotBlank(index)){
                // System.out.println(index);
                String indexs[] = index.split(",");
                for (int i = 0; i < indexs.length; i++) {
                    int n = Integer.parseInt(indexs[i]);//开始下标位置
                    point = new Postion(n, n+str.length() - 1);
                    list.add(point);
                }
            }
        }
        // System.out.println("关键词:" + strs[1]);

        return list;
    }

    /**
     * 获取字符串匹配下标
     * 
     * @author:Ivan
     * @date:2015年11月3日 上午10:53:13
     * @param content
     * @param str
     * @param index
     * @return
     */
    public static String getAllIndex(String content, String str, int index) {
        int n = content.indexOf(str, index);
        if (n != -1) {
            // 递归匹配所有下标位置
            return n + "," + getAllIndex(content, str, n + 1);
        } else {
            return "";
        }
    }

    /**
     * 判断关键词是否为汉字
     * 
     * @author:Ivan
     * @date:2015年11月3日 下午3:05:55
     * @param str
     * @return
     */
    public static boolean isChinese(String str) {
        String regex = "^[\u4e00-\u9fa5]+$";
        return str.matches(regex);
    }

    /**
     * 判断关键词是否包含汉字
     * 
     * @author:Ivan
     * @date:2015年11月3日 下午3:05:55
     * @param str
     * @return
     */
    public static boolean isContainsChinese(String str) {
        Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
        Matcher m = p.matcher(str);
        if (m.find()) {
            return true;
        }
        return false;
    }

}

2.位置标记类

package com.iusoft.sys.utils;

import java.util.HashSet;
import java.util.Set;

/**
 * 位置标记
 * 
 * @author:Ivan
 * @date: 2015年11月3日 下午12:45:08
 */
class Postion {

    public int start = 0;// 开始位置
    public int end = 0;// 结束位置
    private boolean isUnion=false;//是否被合并

    public Postion() {

    }

    public Postion(int start, int end) {
        this.start = start;
        this.end = end;
    }

    /**
     * 位置合并
     * 
     * @author:Ivan
     * @date:2015年11月3日 下午12:51:04
     * @param postion
     * @return
     */
    @SuppressWarnings("serial")
    public boolean union(Postion postion) {
        boolean b = false;
        if (postion != null) {
            Set<Integer> result = new HashSet<Integer>();
            Set<Integer> set1 = new HashSet<Integer>() {
                {
                    for (int i = start; i <= end; i++) {
                        add(i);
                    }
                }
            };

            final int s = postion.start, e = postion.end;
            Set<Integer> set2 = new HashSet<Integer>() {
                {
                    for (int i = s; i <= e; i++) {
                        add(i);
                    }
                }
            };

            //1.取交集
            result.clear();
            result.addAll(set1);
            result.retainAll(set2);
            if (result.size() > 0 ||this.end==postion.start-1 || this.start+1==postion.end) {//判断是否存在交集,存在交集的情况下执行并集操作
                //2.取并集
                result.clear();
                result.addAll(set1);
                result.addAll(set2);
                for (Integer integer : set2) {
                    if (integer < start)
                        start = integer;
                    if (integer > end)
                        end = integer;
                }
                b = true;
                postion.setUnion(b);//标记被合并
            }
        }
        return b;
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getEnd() {
        return end;
    }

    public void setEnd(int end) {
        this.end = end;
    }

    public boolean isUnion() {
        return isUnion;
    }

    public void setUnion(boolean isUnion) {
        this.isUnion = isUnion;
    }

    @Override
    public String toString() {
        return start+","+end+":"+isUnion;
    }


}

3.拼音工具类


package com.iusoft.sys.utils;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

import java.util.Arrays;

/**
 * 拼音工具类
 * @author:Ivan
 * @date: 2015年11月3日 下午6:22:47
 */
public class PinyinTool {
    static HanyuPinyinOutputFormat format = null;
    public static enum Type {
        UPPERCASE,              //全部大写
        LOWERCASE,              //全部小写
        FIRSTUPPER              //首字母大写
    }
    static{
        format = new HanyuPinyinOutputFormat();
        format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
    }

    public PinyinTool(){
        format = new HanyuPinyinOutputFormat();
        format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
    }

    public static String toPinYin(String str){
        return toPinYin(str, "", Type.UPPERCASE);
    }

    public static String toPinYin(String str,String spera){
        return toPinYin(str, spera, Type.UPPERCASE);
    }

    /**
     * 将str转换成拼音,如果不是汉字或者没有对应的拼音,则不作转换
     * 如: 明天 转换成 MINGTIAN
     * @param str
     * @param spera
     * @return
     * @throws BadHanyuPinyinOutputFormatCombination
     */
    public static String toPinYin(String str, String spera, Type type) {
        if(str == null || str.trim().length()==0)
            return "";
        if(type == Type.UPPERCASE)
            format.setCaseType(HanyuPinyinCaseType.UPPERCASE);
        else
            format.setCaseType(HanyuPinyinCaseType.LOWERCASE);

        String py = "";
        String temp = "";
        String[] t=null;
        for(int i=0;i<str.length();i++){
            char c = str.charAt(i);
            if((int)c <= 128)
                py += c;
            else{
                try {
                    t = PinyinHelper.toHanyuPinyinStringArray(c, format);
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    e.printStackTrace();
                }
                if(t == null)
                    py += c;
                else{
                    temp = t[0];
                    if(type == Type.FIRSTUPPER)
                        temp = t[0].toUpperCase().charAt(0)+temp.substring(1);
                    py += temp+(i==str.length()-1?"":spera);
                }
            }
        }
        return py.trim();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值