2011-06-03 26 views
18

我的應用程序需要使用.properties文件進行配置。 在屬性文件中,允許用戶指定路徑。讀取Java屬性文件時無需轉義值

問題

屬性文件需要進行轉義值,例如

dir = c:\\mydir 

所需

我需要一些方法來接受一個屬性文件,其中值沒有逃脫,以便用戶可以指定:

dir = c:\mydir 

回答

18

爲什麼不簡單地擴展屬性類來合併剝離雙正斜槓。這樣做的一個很好的特點是,通過程序的其餘部分,您仍然可以使用原始的Properties類。

public class PropertiesEx extends Properties { 
    public void load(FileInputStream fis) throws IOException { 
     Scanner in = new Scanner(fis); 
     ByteArrayOutputStream out = new ByteArrayOutputStream(); 

     while(in.hasNext()) { 
      out.write(in.nextLine().replace("\\","\\\\").getBytes()); 
      out.write("\n".getBytes()); 
     } 

     InputStream is = new ByteArrayInputStream(out.toByteArray()); 
     super.load(is); 
    } 
} 

使用新類是簡單的:

PropertiesEx p = new PropertiesEx(); 
p.load(new FileInputStream("C:\\temp\\demo.properties")); 
p.list(System.out); 

剝離代碼也可以在改善,但總的原則是存在的。

+0

感謝您的提示。我創建了一個靜態工具類來加載和保存屬性文件,但你的方式更清潔。順便說一句,這就是加載一些netbeans屬性文件(比如project.properties)所需要的,特別是在使用移動包時。 – Snicolas 2011-11-23 07:29:15

6

兩個選項:

  • 使用XML properties格式而不是
  • 作家自己的解析器修改的.properties格式不逃逸
+1

不想要使用XML屬性。 – pdeva 2011-06-11 10:59:00

2

你可以嘗試使用番石榴的Splitter:分裂的'='和構建由此產生的地圖Iterable

該解決方案的缺點是不支持註釋。

+2

另外:嚴格來說,具有未轉義值的'.properties'文件不是爲'.properties'文件指定的格式。格式是比許多人想象的更復雜(例如,它指定始終處於ISO-8859-1編碼,並支持'\ u' Unicode轉義字符)。 – 2011-06-07 07:48:34

6

可以「預處理」加載屬性之前的文件,例如:

public InputStream preprocessPropertiesFile(String myFile) throws IOException{ 
    Scanner in = new Scanner(new FileReader(myFile)); 
    ByteArrayOutputStream out = new ByteArrayOutputStream(); 
    while(in.hasNext()) 
     out.write(in.nextLine().replace("\\","\\\\").getBytes()); 
    return new ByteArrayInputStream(out.toByteArray()); 
} 

而且你的代碼可能看起來這樣

Properties properties = new Properties(); 
properties.load(preprocessPropertiesFile("path/myfile.properties")); 

這樣做,你的屬性文件會是什麼樣子你需要,但你會有屬性值準備使用。 *我知道應該有更好的方法來操作文件,但我希望這有助於。

2

@pdeva:多了一個解決方案

//Reads entire file in a String 
//available in java1.5 
Scanner scan = new Scanner(new File("C:/workspace/Test/src/myfile.properties")); 
scan.useDelimiter("\\Z"); 
String content = scan.next(); 

//Use apache StringEscapeUtils.escapeJava() method to escape java characters 
ByteArrayInputStream bi=new ByteArrayInputStream(StringEscapeUtils.escapeJava(content).getBytes()); 

//load properties file 
Properties properties = new Properties(); 
properties.load(bi); 
+0

如果您正在使用'new Scanner(...)'和'.getBytes()',請確保它們在這裏使用正確的字符集(ISO-8859-1,因爲這是由property.load使用的)。但最好讓性格中的表單中的數據,而不是停留將它們轉換爲字節和背部,即使用StringReader代替ByteArrayInputStream進行的。 – 2011-06-12 03:21:55

3

正確的方法是向用戶提供一個屬性文件編輯器(或者爲自己喜愛的文本編輯器的一個插件),允許他們進入文本爲純文本,並將該文件保存在屬性文件格式中。

如果您不希望這樣,您將爲屬性文件所具有的相同(或部分內容模型)有效定義新格式。

去的整套方法,實際上指定您的格式,再想想辦法要麼

  • 轉換格式,以規範一個,然後用這個加載文件或
  • 解析這種格式並從中填充一個Properties對象。

這兩種方法只有在您實際上可以控制您的屬性對象的創建時纔會直接工作,否則您將不得不使用應用程序存儲轉換後的格式。


所以,讓我們看看我們如何定義這個。的正常屬性文件內容模式很簡單:

  • 串鑰匙的地圖爲字符串值,都允許任意的Java字符串。

要避免逃逸用來只是允許任意Java字符串,而不是隻是其中的一個子集。

一個經常足夠子集將是:

  • 字符串鍵(不含有任何空白,:=)爲字符串值(不含有任何前導或尾隨空格或換行符)的映射。

在你的榜樣dir = c:\mydir,關鍵是dir和值c:\mydir

如果我們希望我們的鍵和值包含任何Unicode字符(除了提及的禁止字符),我們應該使用UTF-8(或UTF-16)作爲存儲編碼 - 因爲我們無法轉義字符存儲編碼之外。否則,US-ASCII或ISO-8859-1(作爲常規屬性文件)或Java支持的任何其他編碼就足夠了,但要確保將其包含在內容模型的規範中(並且確保以這種方式讀取它)。

既然我們限制了我們的內容模式,使所有「危險」字符的方式進行,我們現在可以簡單地定義文件格式如下:

<simplepropertyfile> ::= (<line> <line break>)* 
<line>    ::= <comment> | <empty> | <key-value> 
<comment>   ::= <space>* "#" < any text excluding line breaks > 
<key-value>   ::= <space>* <key> <space>* "=" <space>* <value> <space>* 
<empty>    ::= <space>* 
<key>    ::= < any text excluding ':', '=' and whitespace > 
<value>    ::= < any text starting and ending not with whitespace, 
          not including line breaks > 
<space>    ::= < any whitespace, but not a line break > 
<line break>   ::= < one of "\n", "\r", and "\r\n" > 

\在任一鍵或鍵值發生現在是一個真正的反斜槓,不是任何逃脫別的東西。 因此,將其轉化爲原來的格式,我們只需要雙擊它,就像Grekz建議,例如在過濾讀者:

public DoubleBackslashFilter extends FilterReader { 
    private boolean bufferedBackslash = false; 

    public DoubleBackslashFilter(Reader org) { 
     super(org); 
    } 

    public int read() { 
     if(bufferedBackslash) { 
      bufferedBackslash = false; 
      return '\\'; 
     } 
     int c = super.read(); 
     if(c == '\\') 
      bufferedBackslash = true; 
     return c; 
    } 

    public int read(char[] buf, int off, int len) { 
     int read = 0; 
     if(bufferedBackslash) { 
      buf[off] = '\\'; 
      read++; 
      off++; 
      len --; 
      bufferedBackslash = false; 
     } 
     if(len > 1) { 
      int step = super.read(buf, off, len/2); 
      for(int i = 0; i < step; i++) { 
       if(buf[off+i] == '\\') { 
        // shift everything from here one one char to the right. 
        System.arraycopy(buf, i, buf, i+1, step - i); 
        // adjust parameters 
        step++; i++; 
       } 
      } 
      read += step; 
     } 
     return read; 
    } 
} 

然後我們將這個讀取器傳遞給我們的Properties對象(或保存內容添加到新文件中)。

相反,我們可以簡單地自己分析這個格式。

public Properties parse(Reader in) { 
    BufferedReader r = new BufferedReader(in); 
    Properties prop = new Properties(); 
    Pattern keyValPattern = Pattern.compile("\s*=\s*"); 
    String line; 
    while((line = r.readLine()) != null) { 
     line = line.trim(); // remove leading and trailing space 
     if(line.equals("") || line.startsWith("#")) { 
      continue; // ignore empty and comment lines 
     } 
     String[] kv = line.split(keyValPattern, 2); 
     // the pattern also grabs space around the separator. 
     if(kv.length < 2) { 
      // no key-value separator. TODO: Throw exception or simply ignore this line? 
      continue; 
     } 
     prop.setProperty(kv[0], kv[1]); 
    } 
    r.close(); 
    return prop; 
} 

同樣,使用後此Properties.store(),我們可以在原來的格式導出。

0

這不是一個確切的回答你的問題,但不同的解決方案,可適當您的需求。在Java中,你可以使用/作爲路徑分隔符,它會在Windows,Linux和OSX工作。這對相對路徑特別有用。

在您的例子,你可以使用:

dir = c:/mydir 
+0

我們的用戶不知道使用'/'非手。如果我們必須告訴他們使用'/',我們也可以告訴他們使用\\ – pdeva 2011-07-07 02:35:11

3

基於@Ian哈里根,這裏是一個完整的解決方案,以獲得Netbeans的屬性文件(及其他逃避屬性文件)直接從和ASCII文本文件:

import java.io.BufferedReader; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStream; 
import java.io.OutputStreamWriter; 
import java.io.PrintWriter; 
import java.io.Reader; 
import java.io.Writer; 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 
import java.util.Properties; 

/** 
* This class allows to handle Netbeans properties file. 
* It is based on the work of : http://stackoverflow.com/questions/6233532/reading-java-properties-file-without-escaping-values. 
* It overrides both load methods in order to load a netbeans property file, taking into account the \ that 
* were escaped by java properties original load methods. 
* @author stephane 
*/ 
public class NetbeansProperties extends Properties { 
    @Override 
    public synchronized void load(Reader reader) throws IOException { 
     BufferedReader bfr = new BufferedReader(reader); 
     ByteArrayOutputStream out = new ByteArrayOutputStream(); 

     String readLine = null; 
     while((readLine = bfr.readLine()) != null) { 
      out.write(readLine.replace("\\","\\\\").getBytes()); 
      out.write("\n".getBytes()); 
     }//while 

     InputStream is = new ByteArrayInputStream(out.toByteArray()); 
     super.load(is); 
    }//met 

    @Override 
    public void load(InputStream is) throws IOException { 
     load(new InputStreamReader(is)); 
    }//met 

    @Override 
    public void store(Writer writer, String comments) throws IOException { 
     PrintWriter out = new PrintWriter(writer); 
     if(comments != null) { 
      out.print('#'); 
      out.println(comments); 
     }//if 
     List<String> listOrderedKey = new ArrayList<String>(); 
     listOrderedKey.addAll(this.stringPropertyNames()); 
     Collections.sort(listOrderedKey); 
     for(String key : listOrderedKey) { 
      String newValue = this.getProperty(key); 
      out.println(key+"="+newValue ); 
     }//for 
    }//met 

    @Override 
    public void store(OutputStream out, String comments) throws IOException { 
     store(new OutputStreamWriter(out), comments); 
    }//met 
}//class