解析此配置文件的最佳方法是什么?

时间:2010-08-17 00:20:47

标签: java design-patterns oop configuration-files

我正在开发一个使用自定义配置文件的个人项目。该文件的基本格式如下所示:

[users]
name: bob
attributes:
    hat: brown
    shirt: black
another_section:
    key: value
    key2: value2

name: sally
sex: female
attributes:
    pants: yellow
    shirt: red

可以有任意数量的用户,每个用户可以拥有不同的键/值对,并且使用制表位可以在一个节下面嵌套键/值。我知道我可以为这个配置文件使用json,yaml甚至xml,但是,我现在想保持自定义。

解析应该不难,因为我已经编写了解析它的代码。我的问题是,使用干净结构化代码解析此问题的最佳方法是什么,以及以不会在未来进行更改的方式编写(将来可能会有多个巢穴)。现在,我的代码看起来非常恶心。例如,

private void parseDocument() {  
    String current;
    while((current = reader.readLine()) != null) {
        if(current.equals("") || current.startsWith("#")) {
            continue; //comment
        } 
        else if(current.startsWith("[users]")) {
            parseUsers();
        }
        else if(current.startsWith("[backgrounds]")) {
            parseBackgrounds();
        }
    }
}

private void parseUsers()  {        
    String current;
    while((current = reader.readLine()) != null) {
        if(current.startsWith("attributes:")) {
            while((current = reader.readLine()) != null) {
                if(current.startsWith("\t")) {
                    //add user key/values to User object
                }
                else if(current.startsWith("another_section:")) {
                    while((current = reader.readLine()) != null) {
                        if(current.startsWith("\t")) {
                            //add user key/values to new User object
                        } 
                        else if (current.equals("")) {
                            //newline means that a new user is up to parse next
                        }
                    }
                }
            }
        }
        else if(!current.isEmpty()) {
            //
        }


    }
}

正如您所看到的,代码非常混乱,我在此处简化了演示文稿。我觉得有更好的方法可以做到这一点,也许不使用BufferedReader。有人可以提供一种更好的方式或方法,而不是像我的那样复杂吗?

6 个答案:

答案 0 :(得分:6)

我建议不要为配置文件创建自定义代码。您提出的建议与YAMLgetting started)相距甚远。请改用它。

请参阅Which java YAML library should I use?

答案 1 :(得分:3)

每个人都会建议使用XML,因为它更好。

但是,如果您正在寻求证明您的程序员对自己的价值......

...你所发布的代码没有任何根本性的错误,因为它很清楚,对于潜在的读者而言,显而易见的是,除非我完全脱离文件操作的循环,否则它应该执行几乎可以。

我能提出的一个批评是它不是递归的。每个级别都需要支持新级别的代码。我可能会做一个递归函数(一个用子内容作为参数调用自身的函数,然后再调用子子内容等),可以调用,将所有这些东西读入带有哈希表的哈希表中,然后我将该哈希表用作配置对象。

然后,在那一点上,我可能会停止看到这一点并使用XML。 ;)

答案 2 :(得分:2)

如果您可以使用XML或JSON或其他众所周知的数据编码作为数据格式,则解析/反序列化文本内容并提取值将更加容易。 例如。

name: bob
attributes:
    hat: brown
    shirt: black
another_section:
    key: value
    key2: value2

可以表示为以下XML(还有其他选项可以用XML表示)

<config>
  <User hat="brown" shirt="black" >
    <another_section>
      <key>value</key>
      <key2>value</key2>
    </another_section>
  </User>
</config>

自定义(非常简单) 正如我在下面的评论中提到的,您可以将它们全部设为名称和值对。 e.g。

name                 :bob
attributes_hat       :brown
attributes_shirt     :black
another_section_key  :value
another_section_key2 :value2

然后在'\ n'(换行符)和':'上进行字符串拆分以提取键和值或构建字典/地图对象。

答案 3 :(得分:2)

状态机看起来很简单。

while((current = reader.readLine()) != null) {
  if(current.startsWith("[users]"))
    state = PARSE_USER;
  else if(current.startsWith("[backgrounds]"))
    state = PARSE_BACKGROUND;
  else if (current.equals("")) {
    // Store the user or background that you've been building up if you have one.
    switch(state) {
      case PARSE_USER:
      case USER_ATTRIBUTES:
      case USER_OTHER_ATTRIBUTES:
        state = PARSE_USER;
        break;
      case PARSE_BACKGROUND:
      case BACKGROUND_ATTRIBUTES:
      case BACKGROUND_OTHER_ATTRIBUTES:
        state = PARSE_BACKGROUND;
        break;
    }
  } else switch(state) {
    case PARSE_USER:
    case USER_ATTRIBUTES:
    case USER_OTHER_ATTRIBUTES:
      if(current.startsWith("attributes:"))
        state = USER_ATTRIBUTES;
      else if(current.startsWith("another_section:"))
        state = USER_OTHER_ATTRIBUTES;
      else {
        // Split the line into key/value and store into user
        // object being built up as appropriate based on state.
      }
      break;
    case PARSE_BACKGROUND:
    case BACKGROUND_ATTRIBUTES:
    case BACKGROUND_OTHER_ATTRIBUTES:
      if(current.startsWith("attributes:"))
        state = BACKGROUND_ATTRIBUTES;
      else if(current.startsWith("another_section:"))
        state = BACKGROUND_OTHER_ATTRIBUTES;
      else {
        // Split the line into key/value and store into background
        // object being built up as appropriate based on state.
      }
      break;
  }
}
// If you have an unstored object, store it.

答案 4 :(得分:1)

我建议将配置文件的格式更改为JSON,并使用现有的库来解析FlexJSON等JSON对象。

{
"users": [
    {
        "name": "bob",
        "hat": "brown",
        "shirt": "black",
        "another_section": {
            "key": "value",
            "key2": "value2" 
        } 
    },
    {
        "name": "sally",
        "sex": "female",
        "another_section": {
            "pants": "yellow",
            "shirt": "red" 
        } 
    } 
] 

}

答案 5 :(得分:1)

清理它的一个好方法是使用一个表,即用Map替换你的条件。然后,您可以通过反射(简单)调用解析方法,或者创建一些实现公共接口的类(更多工作但更强大)。