web-dev-qa-db-ja.com

値をエスケープせずにJavaプロパティファイルを読み取る

私のアプリケーションは、構成に.propertiesファイルを使用する必要があります。プロパティファイルでは、ユーザーはパスを指定できます。

問題

プロパティファイルには、エスケープする値が必要です。

dir = c:\\mydir

必要

ユーザーが指定できるように、値がエスケープされていないプロパティファイルを受け入れる方法が必要です。

dir = c:\mydir
19
pdeva

プロパティクラスを単純に拡張して、ダブルスラッシュのストリッピングを組み込んでみませんか。これの良い機能は、プログラムの残りの部分を通して、元の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);

ストリッピングコードも改善される可能性がありますが、一般的な原則はそこにあります。

19
Ian Harrigan

プロパティをロードする前に、ファイルを「前処理」できます。次に例を示します。

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ファイルは必要なように見えますが、プロパティ値をすぐに使用できるようになります。

*ファイルを操作するためのより良い方法があるはずですが、これが役立つことを願っています。

6
Grekz

2つのオプション:

  • 代わりに XMLプロパティ 形式を使用してください
  • エスケープなしで変更された.properties形式の独自のパーサーを作成します
6

正しい方法は、ユーザーにプロパティファイルエディター(またはお気に入りのテキストエディターのプラグイン)を提供することです。これにより、ユーザーはテキストを純粋なテキストとして入力でき、ファイルをプロパティファイル形式で保存できます。

これが必要ない場合は、プロパティファイルと同じ(またはサブセットの)コンテンツモデルの新しい形式を効果的に定義します。

全体に行き、実際にspecifyあなたのフォーマット、そして次にどちらかへの方法を考えてください

  • フォーマットを正規のフォーマットに変換し、これを使用してファイルをロードするか、または
  • この形式を解析し、そこから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が提案したように、たとえばフィルタリングリーダーで2倍にする必要があります。

_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;
    }
}
_

次に、このリーダーをプロパティオブジェクトに渡します(またはコンテンツを新しいファイルに保存します)。

代わりに、この形式を自分で解析するだけで済みます。

_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()を使用して、元の形式でエクスポートできます。

3
Paŭlo Ebermann

@Ian Harriganに基づいて、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
3
Snicolas

@pdeva:もう1つの解決策

//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);
2
ag112

Guavaの Splitter'='で分割し、結果のIterableからマップを作成してみてください。

このソリューションの欠点は、コメントをサポートしていないことです。

2
mindas

それはあなたの質問に対する正確な答えではありませんが、あなたのニーズに適しているかもしれない別の解決策です。 Javaでは、パス区切り文字として/を使用でき、Windows、Linux、およびOSXの両方で機能します。これは、相対パスに特に役立ちます。

あなたの例では、次のものを使用できます。

dir = c:/mydir
0
Daniel Serodio