读取Java属性文件而不转义值
我的应用程序需要使用.properties文件进行配置。 在属性文件中,允许用户指定路径。
问题
属性文件需要转义值,例如
dir = c:\\mydir
需要
我需要一些方法来接受未转义值的属性文件,以便用户可以指定:
dir = c:\mydir
为什么不简单地扩展属性类以包含双正斜杠的剥离。 这个的一个很好的特性是,通过程序的其余部分,您仍然可以使用原始的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);
剥离代码也可以改进,但一般原则是存在的。
两种选择:
- 请改用XML属性格式
- 编写自己的解析器以获得修改后的.properties格式,而无需转义
您可以在加载属性之前“预处理”文件,例如:
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"));
这样做,您的.properties文件看起来就像您需要的那样,但您可以使用属性值。
*我知道应该有更好的方法来操作文件,但我希望这会有所帮助。
正确的方法是为用户提供属性文件编辑器(或他们喜欢的文本编辑器的插件),允许他们以纯文本forms输入文本,并以文件格式保存文件。
如果您不想这样做,那么您正在为属性文件的内容模型的相同(或子集)有效地定义新格式。
走完全路并实际指定你的格式,然后考虑一种方法
- 将格式转换为规范格式,然后使用它来加载文件,或
- 解析此格式并从中填充
Properties
对象。
这两种方法只有在您实际可以控制属性对象的创建时才能直接工作,否则您必须将转换后的格式存储在应用程序中。
那么,让我们看看我们如何定义它。 普通属性文件的内容模型很简单:
- 字符串键到字符串值的映射,两者都允许任意Java字符串。
您想要避免的转义仅用于允许任意Java字符串,而不仅仅是这些字符串的子集。
通常足够的子集是:
- 字符串键(不包含任何空格,
:
或=
)到字符串值(不包含任何前导或尾随空格或换行符)的映射。
在你的例子中, dir = c:\mydir
,键是dir
,值是c:\mydir
。
如果我们希望我们的键和值包含任何Unicode字符(除了提到的禁用字符),我们应该使用UTF-8(或UTF-16)作为存储编码 – 因为我们无法转义存储之外的字符编码。 否则,US-ASCII或ISO-8859-1(作为普通属性文件)或Java支持的任何其他编码就足够了,但请确保将其包含在您的内容模型规范中(并确保以这种方式阅读) )。
由于我们限制了我们的内容模型,以便所有“危险”字符都不受影响,我们现在可以简单地定义文件格式:
::= ( )* ::= | | ::= * "#" < any text excluding line breaks > ::= * * "=" * * ::= * ::= < any text excluding ':', '=' and whitespace > ::= < any text starting and ending not with whitespace, not including line breaks > ::= < any whitespace, but not a 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; } }
然后我们将此Reader传递给我们的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()
,我们可以以原始格式导出它。
基于@Ian Harrigan,这是一个完整的解决方案,可以从ascii文本文件中获取Netbeans属性文件(以及其他转义属性文件):
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 listOrderedKey = new ArrayList (); 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
您可以尝试使用guava的Splitter
:拆分'='
并从生成的Iterable
构建一个映射。
此解决方案的缺点是它不支持注释。
@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);
这不是您问题的准确答案,而是可能适合您需求的不同解决方案。 在Java中,您可以使用/
作为路径分隔符,它可以在Windows,Linux和OSX上运行。 这对于相对路径特别有用。
在您的示例中,您可以使用:
dir = c:/mydir