繁体   English   中英

读取Java属性文件而不转义值

[英]Reading Java Properties file without escaping values

我的应用程序需要使用.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文件看起来就像您需要的那样,但您可以使用属性值。

*我知道应该有更好的方法来操作文件,但我希望这会有所帮助。

正确的方法是为用户提供属性文件编辑器(或他们喜欢的文本编辑器的插件),允许他们以纯文本形式输入文本,并将文件保存为属性文件格式。

如果您不想这样做,那么您实际上正在为属性文件所具有的内容模型的相同(或子集)定义新格式。

走完全程并实际指定你的格式,然后考虑一种方法

  • 将格式转换为规范格式,然后使用它来加载文件,或
  • 解析此格式并从中填充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;
    }
}

然后我们将此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<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

您可以尝试使用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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM