使用java操作Excel替换xml中的翻译文案

一、概述

海外APP,经常会翻译国外语言,项目中间突然加入一门新语言,一般海外市场部会提供一个翻译好的Excel表格,里边包含了所有的字符串和对应的翻译,如下所示:

如果项目不断迭代,对应的字符串也可能会有N多个表。手动翻译翻译,需要在项目的strings.xml文件和Excel表格中来回切换,逐条搜索替换,很不方便,使用java语言,来操作Excel表格,代码处理,可以减少了繁琐劳动,降低出错率。

需要考虑的问题很多,大致如下:

1.使用java,怎么操作Excel文件,不仅要读取,匹配好的数据也要放入一个表格里,此时就涉及到了写

2.操作时,对Excel文件的格式有个要求,不同格式的Excel文件处理时,有何不同

3.第一次处理,共用到三个Excel文件:

    1). 翻译好的文件,即源文件,ori.xls。里边都是单纯的翻译,不包含<string name=""></string>这种样式。

    2). 复制strings.xml内容到Excel文件中,需要比对的文件,des.xls。它是含有<string name="nameStr">contentStr</string>的字符串。

    3). 以翻译乌克兰语为例,得到ori.xls里C、Q两列的内容后,再获取des.xls中每个单元格的内容,解析出nameStr和contentStr。根据contentStr,和ori.xls里的结果匹配比较,找到contentStr在ori.xls中的行列坐标,这样就可以推知乌克兰语的行列坐标。重新组装为<string name="nameStr">translate content</string>,放入结果文件result.xls中。

4. 此时要考虑des.xls中每条string的内容格式和结果文件result.xls的内容格式。

    1).怎么截取name和content部分,最终格式为: <string name="nameStr">translate content</string>

    2).该条若注释掉了,直接跳过

    3).获取到content后,要判断是否被双引号""包围,若没被双引号包围,出现了 ' 要转义为 \'

    4).content中若包含非英语形式的特殊符号,如?!-等,要替换处理

    5). Excel文件中的&在xml中表示为 &amp;,也要处理

等等。

二、读Excel文件

// 创建file对象
File file = new File(filePath);
// 创建输入流,读取Excel
InputStream is = new FileInputStream(file);
// jxl提供的Workbook类
Workbook wb = Workbook.getWorkbook(is);
//获取第一个sheet
Sheet sheet = wb.getSheet(0);
Log.d(TAG, "readOriExcel: sheet name = "+ sheet.getName());
// 得到所有的行数
int rows = sheet.getRows();
for (int j = 0; j < rows; j++) {
	// 得到这行每个单元格的数据
	Cell[] cells = sheet.getRow(j);
}

三、写Excel文件

/**
 * 将数据写入到excel中
 */
public static  void writeExcel() {
	//1 创建一个workbook对应一个excel文件
	HSSFWorkbook workbook = new HSSFWorkbook();
	//2 在workbook中创建一个sheet对应excel中的sheet
	HSSFSheet sheet = workbook.createSheet(name);

	for (int i = 0; i < desSheetAllData.size(); i++) {
		HSSFRow row = sheet.createRow(i);
		List<String> rowData = desSheetAllData.get(i);
		for (int j = 0; j < rowData.size(); j++) {
			//3 创建单元格并设值
			row.createCell(j).setCellValue(rowData.get(j));
		}
	}

	FileOutputStream fos = null;
	try {
		fos = new FileOutputStream(PATH+"/result.xls");
		workbook.write(fos);
	} catch (IOException e) {
		Log.d(TAG, "writeExcel: catch - " + e.getMessage());
	} finally {
		try {
			//数据量较大,使用flush刷新该流的缓冲区
			//一般写字符时要用,因为字符是先进入的缓冲区
			fos.flush();
			//关闭流对象
			fos.close();
		} catch (IOException e) {
			Log.d(TAG, "writeExcel: finally -" + e.getMessage());
		}
	}
	Log.d(TAG, "writeExcel: success!!!");
}

注:此处使用HSSFWorkbook创建的是xls文件,若创建xlsx文件,要用XSSFWorkbook。 HSSFWorkbook和XSSFWorkbook 类都实现了Workbook接口。

四、解析字符串 <string name="nameStr">contentStr</string>

1.利用正则表达式"<[^>]+>"解析出contentStr。

String str = "<string name=\"nameStr\">contentStr</string>";
// 定义xml标签的正则表达式
String regExp = "<[^>]+>";
// 过滤xml标签
String contentStr = Pattern.compile(regExp).matcher(str).replaceAll("");

2. 利用"\">"来解析出<string name="nameStr来。

String[] split = str.split("\">");
String nameStr = split[0];

根据contentStr找到对应的翻译以后,再把<string name="nameStr补充完整:<string name="nameStr">translate content</string>

五、处理要替换的内容contentStr

1. 汉语中的?!- 等,使用replaceAll()方法。注意,调用replaceAll()方法的使用方式:

content = content.replaceAll("\'", "'");

必须接收一次replaceAll返回的字符串,这样,才是处理后,想要的结果。

2. 若想得到某个字符,如-的ASIIC码用(int)('-')就可以得到。

3. 去掉首尾的引号,使用substring()方法。

content = content.substring(1, content.length() - 1);//去掉首尾的引号

str.length()-1为字符串最后一位的下标,它作为substring()的参数2,为开区间,表示不包含最后一位。参数1位闭区间,包含第一位。故:

String test = "0123456";
Log.d(TAG, "lym12345: " + test.substring(0, test.length()-1));// --> 012345
Log.d(TAG, "lym12345: " + test.substring(1, test.length()-2));// --> 1234

4. 若contentStr中含有\",比如:open \"location\",在英语和乌克兰语匹配过程中,是以open "location"来匹配的,此时要把\"替换为",即把\去掉

//处理句中的\"
if (content.contains("\\\"")){
	content = content.replaceAll("\\\\", "");
}

java中,\用\\表示,"用\"表示,\"则用\\\"表示。正则表达式中"\\\\"表示\。

\' 替换 ',\" 替换 ":

//没有双引号的时候,遇到'再用\'替换,遇到"再用\"替换
if (content.contains("'")) {
	content = content.replace("'", "\\'");
} else if (content.contains("\"")){
	content = content.replace("\"", "\\\"");
}

5. 去除字符串前后的空格,java的string.trim()只能去英文半角空格

public class StringUtil {
 
    /**普通的英文半角空格Unicode编码*/
    private static final int SPACE_32 = 32;
 
    /**中文全角空格Unicode编码(一个中文宽度)*/
    private static final int SPACE_12288 = 12288;
 
    /**普通的英文半角空格但不换行Unicode编码(== &nbsp; == &#xA0; == no-break space)*/
    private static final int SPACE_160 = 160;
 
    /**半个中文宽度(== &ensp; == en空格)*/
    private static final int SPACE_8194 = 8194;
 
    /**一个中文宽度(== &emsp; == em空格)*/
    private static final int SPACE_8195 = 8195;
 
    /**四分之一中文宽度(四分之一em空格)*/
    private static final int SPACE_8197 = 8197;
 
    /**窄空格*/
    private static final int SPACE_8201 = 8201;
 
    /**
     * 去除字符串前后的空格, 包括半角空格和全角空格(中文)等各种空格, java的string.trim()只能去英文半角空格
     * @param str
     */
    public static String trim(String str) {
        if (TextUtils.isEmpty(str)) {
            return str;
        }
 
        char[] val = str.toCharArray();
        int st = 0;
        int len=val.length;
        while ((st < len) && isSpace(val[st])) {
            st++;
        }
        while ((st < len) && isSpace(val[len - 1])) {
            len--;
        }
        return ((st > 0) || (len < val.length)) ? str.substring(st, len) : str;
    }
 
    private static boolean isSpace(char aChar) {
        return aChar == SPACE_32 || aChar == SPACE_12288 || aChar == SPACE_160 || aChar == SPACE_8194
                || aChar == SPACE_8195 || aChar == SPACE_8197 || aChar == SPACE_8201;
    }
}

六、其他

1. 每次写入时有上限,不要多余150行;

2. 为便于查看,Excel中不要空行;

3. 读写SD卡里的文件,打开操作后的Excel,提示: 无法打开文件,可以尝试重启手机、AndroidStudio

七、参考

HSSFworkbook,XSSFworkbook,SXSSFworkbook区别总结

Java 正则匹配html标签

JAVA 正则表达式 (超详细)

正则表达式之分组 group(java版)

浅谈JAVA中流的flush()&close()方法

close()和flush()的区别

八、代码附上

public class Excel {
    private static final String TAG = "lym1234";
    private static String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xml/";
    private static String DES_FILE = "des.xls";
    private static String ORI_FILE = "ori.xls";
    private static int indexR = 0;
    private static int indexC = 0;

    private static final int DOT = 1;//.
    private static final int QUESTION_MARK = 2;//?
    private static final int POINT = 3;//!
    
    private static List<List<String>> oriSheetAllData = new ArrayList<>(); // ori所有的数据
    private static List<List<String>> desSheetAllData = new ArrayList<>(); // des所有的数据
    
    public static void operate() {
        try {
            oriSheetAllData.clear();
            desSheetAllData.clear();
            readOriExcel();
            readDesExcel();
            writeExcel();
        } catch (Exception e) {
            Log.d(TAG, "e: " + e.getMessage());
        }
    }
    
    private static void readOriExcel() throws Exception {
        // 读取Excel文件
        File file = new File(PATH+ORI_FILE);
        // 创建输入流,读取Excel
        InputStream is = new FileInputStream(file.getAbsolutePath());
        // jxl提供的Workbook类
        Workbook wb = Workbook.getWorkbook(is);
        int sheetNum = wb.getNumberOfSheets();
        Log.d(TAG, "readOriExcel: sheetNum = " + sheetNum);

        for (int i = 0; i < sheetNum; i++){
            Sheet sheet = wb.getSheet(i);//获取每个sheet
            Log.d(TAG, "readOriExcel: sheet name = "+ sheet.getName());
            // 得到所有的行数
            int rows = sheet.getRows();
            Log.d(TAG, "readOriExcel: rows = " + rows);

            // 越过第一行 它是列名称
            for (int j = 1; j < rows; j++) {
                List<String> oneData = new ArrayList<>();
                // 得到每一行的单元格的数据
                Cell[] cells = sheet.getRow(j);
                Log.d(TAG, "readOriExcel: cols = " + cells.length);
                for (int k = 0; k < cells.length; k++) {
                    // 读取每行的第3/17/18列元素
                    // 若Excel表格中,第5列的数据全部为空,则第一行的第5个单元格要填上数据,不能为空。
                    // 否则,从第二行开始,每行下标为4的数据应该为空,就会变成第6个单元格的内容,
                    // 且读取的总长度会减1
                    if (2 == k || 16 == k || 17 == k) {
                        oneData.add(StringUtil.trim(cells[k].getContents()));
                    }
                }
                // 存储每一条数据
                oriSheetAllData.add(oneData);
            }
        }
    }

    private static void readDesExcel() throws  Exception{
        boolean beginWithDoubleQuotes = false;//被双引号包围
        boolean haveDoubleQuotes = false;//句中有双引号
        int endWithSpecialChar = 0;

        // 读取Excel文件
        File file = new File(PATH+DES_FILE);
        // 创建输入流,读取Excel
        InputStream is = new FileInputStream(file.getAbsolutePath());
        // jxl提供的Workbook类
        Workbook wb = Workbook.getWorkbook(is);
        Sheet sheet = wb.getSheet(0);//获取每个sheet
        Log.d(TAG, "readOriExcel: sheet name = "+ sheet.getName());
        // 得到所有的行数
        int rows = sheet.getRows();
        Log.d(TAG, "readOriExcel: rows = " + rows);
        Log.d(TAG, "readDesExcel: " + (int)('–') + " : " + (int)('-'));
        for (int j = 0; j < rows; j++) {
            List<String> oneData = new ArrayList<>();
            // 得到每一行的单元格的数据
            Cell[] cells = sheet.getRow(j);
            if (0 == cells.length){//空的单元格
                oneData.add(" ");
                oneData.add(" ");
                continue;
            }
            //Log.d(TAG, "readOriExcel: cols = " + cells.length);
            for (int k = 0; k < 1/*cells.length*/; k++) {
                String str = StringUtil.trim(cells[k].getContents().trim());
                if (str.startsWith("<!--") || TextUtils.isEmpty(str)){
                    Log.d(TAG, "readDesExcel: 注释 or empty - " + str);
                    oneData.add(str);//注释掉的和空的,原样输出,占位用
                    oneData.add(str);
                    continue;
                }

                String[] split = str.split("\">");
                //Log.d(TAG, "readDesExcel: " + split[0] + split[1]);
                String name = split[0];

                String regExpContent = "<[^>]+>";
                String content = Pattern.compile(regExpContent).matcher(str).replaceAll("");
                Log.d(TAG, "readDesExcel: content1 = " + content);

                if (content.equalsIgnoreCase("\"\"")){
                    Log.d(TAG, "readDesExcel: content is \"\"");
                    oneData.add(str);
                    oneData.add(str);
                    continue;
                }
                //是否是双引号开头
                if (content.startsWith("\"")){
                    beginWithDoubleQuotes = true;
                    content = content.substring(1, content.length() - 1);//去掉首尾的引号
                } else {
                    //去掉转义字符\
                    beginWithDoubleQuotes = false;
                    if (content.contains("\'")){
                        content = content.replaceAll("\'", "'");
                    }
                }

                //是否以.?!等结尾
                if (content.contains("?")){//替换中文下的?!
                    content = content.replaceAll("?", "?");
                }
                if (content.contains("!")){
                    content = content.replaceAll("!", "!");
                }
                if (content.endsWith(".") || content.endsWith("?") || content.endsWith("!")){
                    endWithSpecialChar = content.endsWith(".") ?
                            DOT : (content.endsWith("?") ? QUESTION_MARK : POINT);
                    content = content.substring(0, content.length()-1);
                } else {
                    endWithSpecialChar = 0;
                }


                //替换汉字中的-
                if (content.contains("–")){
                    content = content.replaceAll("–", "-");
                }

                //替换&amp;
                if (content.contains("&amp;")){
                    content = content.replaceAll("&amp;", "&");
                }

                //去掉,后的空格
                if (content.contains(",")){//替换中文下的,
                    content = content.replaceAll(",", ",");
                }
                if (content.contains(", ")){
                    content = content.replaceAll(", ", ",");
                }

                //处理句中的\"
                if (content.contains("\\\"")){
                    haveDoubleQuotes = true;
                    content = content.replaceAll("\\\\", "");
                } else {
                    haveDoubleQuotes = false;
                }
                Log.d(TAG, "readDesExcel: content2 = " + content);

                handlerStr(content);

                if (0 == indexR || 0 != indexC){//未找到对应的翻译
                    Log.d(TAG, "readDesExcel: indexR == 0 or indexC = " + indexC);
                    oneData.add(str);
                    oneData.add(str);
                    continue;
                }

                String ukStr = getContentStr(name, beginWithDoubleQuotes,
                        haveDoubleQuotes, endWithSpecialChar, 1);
                Log.d(TAG, "readDesExcel: ukStr = " + ukStr);

                String roStr = getContentStr(name, beginWithDoubleQuotes,
                        haveDoubleQuotes, endWithSpecialChar, 2);
                Log.d(TAG, "readDesExcel: roStr = " + roStr);
                oneData.add(ukStr);
                oneData.add(roStr);
            }
            // 存储每一条数据
            desSheetAllData.add(oneData);
        }
        // 打印出每一条数据
        //Log.d(TAG, desSheetAllData.toString().trim());
    }


    //两个文件的原字符串比较,获取字符串的行下标
    private static void handlerStr(String content) {
        indexR = 0;
        indexC = 0;
        boolean find = false;
        for (int i = 0; i < oriSheetAllData.size(); i++){
            if (find){
                break;
            }
            for (int j = 0; j < oriSheetAllData.get(i).size(); j++) {
                String oriStr = oriSheetAllData.get(i).get(j).trim();
                if (oriStr.startsWith("\"")){//被双引号包围
                    oriStr = oriStr.substring(1, oriStr.length()-1);
                }

                //是否以.?!等结尾
                if (oriStr.contains("?")){//替换中文下的?!
                    oriStr = oriStr.replaceAll("?", "?");
                }
                if (oriStr.contains("!")){
                    oriStr = oriStr.replaceAll("!", "!");
                }
                if (oriStr.endsWith(".") || oriStr.endsWith("?") || oriStr.endsWith("!")) {//以.结尾
                    oriStr = oriStr.substring(0, oriStr.length() - 1);
                }
                //去掉,后的空格
                if (oriStr.contains(",")){//替换中文下的,
                    oriStr = oriStr.replaceAll(",", ",");
                }
                if (oriStr.contains(", ")){
                    oriStr = oriStr.replaceAll(", ", ",");
                }
                //处理句中的\"
                if (oriStr.contains("\\\"")) {
                    //oriStr = oriStr.replaceAll("\\\"", "\"");
                    content = content.replaceAll("\\\\", "");
                }
                if (oriStr.equalsIgnoreCase(content)) {
                    indexR = i;
                    indexC = j;
                    find = true;
                    break;
                } else {
                    find = false;
                }
            }
        }
        //Log.d(TAG, "handlerStr: " + oriSheetAllData.get(indexR));
        //Log.d(TAG, "handlerStr: " + oriSheetAllData.get(indexR).get(indexC));
    }

    //获取翻译的字符串 乌克兰
    private static String getContentStr(String name, boolean beginWithDoubleQuotes,
                                        boolean haveDoubleQuotes, int endWithSpecialChar, int i){
        Log.d(TAG, "getContentStr: name = " + name + ", double = " + beginWithDoubleQuotes);
        String content = oriSheetAllData.get(indexR).get(indexC+i);

        //替换中文下的?!
        if (content.contains("?")){
            content = content.replaceAll("?", "?");
        }
        if (content.contains("!")){
            content = content.replaceAll("!", "!");
        }
        //替换&,xml文件才可以识别
        if (content.contains(" & ")) {
            content = content.replaceAll(" & ", " &amp; ");
        }

        //被双引号包围
        if (!beginWithDoubleQuotes) {
            //没有双引号的时候,遇到'再用\'替换,遇到"再用\"替换
            if (content.contains("'")) {
                content = content.replace("'", "\\'");
            } else if (content.contains("\"")){
                content = content.replace("\"", "\\\"");
            }
            //以特殊符号结尾
            if (DOT == endWithSpecialChar && !content.endsWith(".")){
                content = content.concat(".");
            }
            if (QUESTION_MARK == endWithSpecialChar && !content.endsWith("?")){
                content = content.concat("?");
            }
            if (POINT == endWithSpecialChar && !content.endsWith("!")){
                content = content.concat("!");
            }
        } else {
            //句中有双引号,还被双引号包围,此时,句中的双引号要带\
            //若都带\",则不需要处理
//            if (content.){
//                content.contains("")
//            }
            //有双引号,则补上
            if (!content.startsWith("\"")) {
                content = "\"" + content + "\"";
            }

        }
        //Log.d(TAG, "getContentStr: content = " + content);

        String contentStr = name+"\">"+content+"</string>";
        //Log.d(TAG, "getContentStr: contentStr = " + contentStr);
        return contentStr;
    }

    private static boolean test = false;
    /**
     * 将数据写入到excel中
     */
    public static  void writeExcel() {

        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH_mm_ss");
        String timeStr = sdf.format(d);
        //System.out.println("格式化后的日期:" + dateNowStr);

        //第一步,创建一个workbook对应一个excel文件
        HSSFWorkbook workbook = new HSSFWorkbook();
        //第二部,在workbook中创建一个sheet对应excel中的sheet
        HSSFSheet sheet = workbook.createSheet(timeStr);

        if (test){
            //第三部,在sheet表中添加表头第0行,老版本的poi对sheet的行列有限制
            HSSFRow row = sheet.createRow(0);
            //第四步,创建单元格,设置表头
            HSSFCell cell = row.createCell(0);
            cell.setCellValue("date1");
            cell = row.createCell(1);
            cell.setCellValue("month1");
            cell = row.createCell(2);
            cell.setCellValue("weekday1");
            cell = row.createCell(3);
            cell.setCellValue("毫升/单位1");
        } else {
            Log.d(TAG, "writeExcel: rowCnt = " + desSheetAllData.size());
            for (int i = 0; i < desSheetAllData.size(); i++) {
                Log.d(TAG, "writeExcel: row " + i);
                HSSFRow row = sheet.createRow(i);

                List<String> rowData = desSheetAllData.get(i);
                for (int j = 0; j < rowData.size(); j++) {
                    //创建单元格设值
                    Log.d(TAG, "writeExcel: col " + j + " : " + rowData.get(j));
                    row.createCell(j).setCellValue(rowData.get(j));
                }
            }
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(PATH+"/result.xls");
            workbook.write(fos);
        } catch (IOException e) {
            Log.d(TAG, "writeExcel: catch - " + e.getMessage());
        } finally {
            try {
                //数据量较大,使用flush刷新该流的缓冲区
                //一般写字符时要用,因为字符是先进入的缓冲区
                fos.flush();
                //关闭流对象
                fos.close();
            } catch (IOException e) {
                Log.d(TAG, "writeExcel: finally -" + e.getMessage());
            }
        }
        Log.d(TAG, "writeExcel: success!!!");
    }    
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值