文件對比與差異提取的實現


File Comparison Tool

 

前言

  一款需要多國語言的應用(真正受歡迎的至少需要中文和英文,不管是PC端還是移動端),那么應用程序開發過程中定義的字串文件就需要進行翻譯,而這項工作在大公司往往是多人(甚至是多部門)協同完成。比如移動端開發常見的為strings.xml,默認是在res/values目錄下,嚴格來說該目錄對應的是英文環境,而中文簡體環境規范的目錄為res/values-zh-rCH。

 

需求

  那么問題來了,開發人員定義好應用中需要用到的字串(最初可能是英文,也可能是中文)並且送翻(提交給專門的翻譯人員進行字串翻譯)后,怎么判斷返回的翻譯結果是完整的呢?或者說,中途接手一個項目,怎么快速掌握原來的字串翻譯情況呢?

  之前有篇文章講過Lint工具可以分析出項目中資源冗余及翻譯不全等問題,但給出的信息不夠簡潔,用其來完成專門的字串翻譯文件對比並不是很合適。通過下圖可以看出,對於某一條字串的翻譯缺失,Lint給出的信息是什么樣的。

  協同工作的好處是,項目文件的commit、pull、push及review均可以在項目管理工具或者工具對應的命令行環境中進行。而review還可以在項目對應的遠程網頁中進行,便於管理者查看新提交的文件到底進行了哪些改動。但是其和文件比較工具(如beyond compare)類似,只能一個一個文件地查看,若想提取出所有的修改結果(或者不同點),只能認為地復制與黏貼到自己建立的文件中。

  所以,若想以最初的strings.xml為基礎,查看其它語言環境的翻譯情況,並將未翻譯的字串統一寫入到一個特定的文件中,就需要自己進行實現了。

 

實現

  用來實現該功能的語言平台為Eclipse中的Java,過程大致可以分為四步:

  1、  以項目res目錄為起始路徑,獲取其中包含的所有子文件夾;

  2、  在1的基礎上,獲取名稱中包含values的文件夾,因為其他文件夾和strings.xml無關(當然,這是一般而言的);

  3、  在2的基礎上,獲取存在的strings.xml的完整路徑;

  4、  文件內容對比,針對最初的strings.xml文件中的某一條字串(需要翻譯的,即不包括文件定義頭<?xml ... ?>、<resources>…</resources>及顯示說明不需翻譯的字串等),若其他語言環境對應的strings.xml文件中不存在該字串的翻譯,那么就將該字串的行號和內容寫入結果文件。為了方便閱讀和修改,結果是以文件為單位,而不是像Lint工具那樣以字串為單位,即原來是某條字串未進行翻譯的國家有A、B、C、D、……,而現在是某個語言環境未翻譯的字串有A、B、C、D、……;

  完整的Java實現代碼如下:

  1 package com.filediff;
  2 
  3 import java.awt.image.WritableRaster;
  4 import java.io.BufferedReader;
  5 import java.io.File;
  6 import java.io.FileNotFoundException;
  7 import java.io.FileReader;
  8 import java.io.FileWriter;
  9 import java.io.IOException;
 10 import java.io.Writer;
 11 import java.time.chrono.JapaneseChronology;
 12 import java.util.ArrayList;
 13 import java.util.Date;
 14 
 15 import javax.swing.JFrame;
 16 
 17 @SuppressWarnings("serial")
 18 public class FileDiff extends JFrame {
 19 
 20     //resources root path
 21     private static String rootDir = "res";
 22     //directory name values
 23     private static String dirFilter = "values";
 24     //file name
 25     private static String fileName = "strings.xml";
 26     //all directory in rootDir
 27     private static String[] subDirs = null;
 28     //all directory(name contain values) in rootDir
 29     private static ArrayList<String> valuesDirs = new ArrayList<String>();
 30     //all file full names as strings.xml
 31     private static ArrayList<String> fileFullNames = new ArrayList<String>();
 32     //result file name
 33     private static String result = "Result.xml";
 34     private static int fileCounts = 0;
 35     private static int fileIndex = 0;
 36     private static String indicate = "name=";
 37     
 38     private static String preDate = new Date().toString()+"\n\n";
 39 
 40     public static void main(String[] args) throws FileNotFoundException {
 41         getSubDirs();
 42         getValuesDirs();
 43         getfileFullNames();
 44         
 45         fileCounts = fileFullNames.size();
 46         
 47         //打開一個寫文件器,構造函數中的第二個參數true表示以追加形式寫文件
 48         FileWriter writer;
 49         try {
 50             writer = new FileWriter(result);
 51             writer.write(preDate);
 52             writer.write("與"+fileFullNames.get(0)+"相比, ");
 53             writer.close();
 54         } catch (IOException e) {
 55             // TODO Auto-generated catch block
 56             e.printStackTrace();
 57         }
 58         //fileCounts
 59         for(fileIndex=1;fileIndex<fileCounts;++fileIndex){
 60             compareFiles();
 61         }
 62     }
 63     
 64     private static void compareFiles(){
 65         FileWriter writer;
 66         try {
 67             writer = new FileWriter(result, true);
 68             String preContent2 = "\n\n"+fileFullNames.get(fileIndex)+"未翻譯的字串為: \n";
 69             writer.write(preContent2);
 70             
 71             File fileSrc = new File(fileFullNames.get(0));
 72             File fileDes = new File(fileFullNames.get(fileIndex));
 73             BufferedReader readerSrc = null;
 74             BufferedReader readerDes = null;
 75             try {
 76                 readerSrc = new BufferedReader(new FileReader(fileSrc));
 77                 String stringSrc = null;
 78                 String stringDes = null;
 79                 boolean contantFlag = false;
 80                 int line = 1;
 81                 // 一次讀入一行,直到讀入null為文件結束
 82                 while ((stringSrc = readerSrc.readLine()) != null) {
 83                     if(stringSrc.contains(indicate) && !stringSrc.contains("translatable=\"false\"")){
 84                         String str1 = stringSrc.substring(stringSrc.indexOf("=")+2);
 85                         String str2 = str1.substring(0,str1.indexOf("\""));
 86                         readerDes = new BufferedReader(new FileReader(fileDes));
 87                         contantFlag = false;
 88                         while ((stringDes = readerDes.readLine()) != null) {
 89                             if(stringDes.contains(str2)){
 90                                 contantFlag = true;
 91                                 break;
 92                             }
 93                         }
 94                         if (!contantFlag) {
 95                             writer.write("Line number and content is: "+line+", "+stringSrc+"\n");
 96                         }
 97                     }
 98                     line++;
 99                 }
100                 readerSrc.close();
101             } catch (IOException e) {
102                 e.printStackTrace();
103             } finally {
104                 if (readerSrc != null) {
105                     try {
106                         readerSrc.close();
107                     } catch (IOException e1) {
108                     }
109                 }
110             }
111                         
112             writer.close();
113         } catch (IOException e) {
114             // TODO Auto-generated catch block
115             e.printStackTrace();
116         }
117     }
118     
119     //get all directory in rootDir
120     private static void getSubDirs(){
121         File fileDirRoot = new File(rootDir);
122         subDirs = fileDirRoot.list();
123     }
124     
125     //get all directory(name contain values) in rootDir
126     private static void getValuesDirs(){
127         for(int i=0;i<subDirs.length;++i){
128             if(subDirs[i].contains(dirFilter)){
129                 valuesDirs.add(subDirs[i]);
130             }
131         }
132     }
133     
134     //get all fileFullNames(contain strings.xml) in rootDir
135     private static void getfileFullNames(){
136         for(int i=0;i<valuesDirs.size();++i){
137             File file = new File(rootDir+"\\"+valuesDirs.get(i)+"\\"+fileName);
138             if(file.exists()){
139                 fileFullNames.add(rootDir+"\\"+valuesDirs.get(i)+"\\"+fileName);
140             }
141         }
142     }
143 }

  從代碼可以看出,以上描述的四個步驟對應的方法分別為getSubDirs()、getValuesDirs()、getfileFullNames()、compareFiles()。

  在結果文件Result.xml的開頭,寫入了當前時間,其對應字串的獲取方式為:new Date().toString()。

  注意,如代碼138-140行所示,在將某個語言環境對應的strings.xml文件完整名稱添加入ArrayList對象fileFullNames中之前,對其存在性進行了判斷,也可以在后面的文件比較環節進行個文件的存在性判斷,但不能不判斷就進行文件的讀取(多余,也容易引起異常)。

  res目錄下的文件夾獲取情況:

  res目錄下的名稱中包含values的文件夾獲取情況:

  res目錄下的名稱中包含values的文件夾中strings.xml的完整名稱獲取情況:

  所有翻譯字串最終的對比結果文件Result.xml的部分內容為:

   有了這樣的描述,就很容易查看當前多國語言的翻譯情況了。這里的字串是隨意敲的,不然命名的level也太低了。

 

總結

  雖然實現的功能具有局限性,但已經可以滿足上面的需求。正是因為一般通用的工具不能完成項目開發過程中遇到的所有情況,所以才要自己動手針對特定的需求進行實現。這篇文章只是起到拋磚引玉的作用,對於其他各種各樣的場景,實現的思路還是相同的。

  歡迎感興趣的朋友一起討論、學習與進步!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM