1 /** 2 * JavaProperties Loader 3 * 4 * Copyright: (c) 2015-2016, Milofon Project. 5 * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file. 6 * Authors: Maksim Galanin 7 */ 8 module proped.loaders.properties; 9 10 private 11 { 12 import std.algorithm.iteration : map, filter; 13 import std.exception : enforceEx; 14 import std.file : FileException, readText; 15 import std.typecons : Tuple, tuple; 16 import std..string : splitLines; 17 import std.array : front; 18 import std.conv : to; 19 20 import proped.properties : Properties, PropNode; 21 import proped.loader : PropertiesLoader; 22 import proped.exception : PropertiesException; 23 } 24 25 26 /** 27 * The loader data from a .properties file 28 * 29 * Implements PropertiesLoader 30 */ 31 class PropertiesPropertiesLoader : PropertiesLoader 32 { 33 /** 34 * Loading properties from a file 35 * 36 * Params: 37 * 38 * fileName = Path to the file system 39 */ 40 Properties loadPropertiesFile(string fileName) 41 { 42 try 43 { 44 string source = readText(fileName); 45 return loadPropertiesString(source); 46 } 47 catch (FileException e) 48 throw new PropertiesException("Error loading properties from a file '" ~ fileName ~ "':", e); 49 } 50 51 52 /** 53 * Loading properties from a string 54 * 55 * Params: 56 * 57 * data = Source string 58 */ 59 Properties loadPropertiesString(string data) 60 { 61 return toProperties(data); 62 } 63 64 65 private Properties toProperties(string data) 66 { 67 PropNode[string] map; 68 Properties result = Properties(PropNode(map)); 69 alias Pair = Tuple!(string, "key", string, "value"); 70 71 Pair parsePair(string line) 72 { 73 size_t keyLen; 74 char c; 75 size_t valueStart = line.length; 76 bool hasSep = false; 77 bool precedingBackslash = false; 78 size_t limit = line.length; 79 80 while (keyLen < limit) 81 { 82 c = line[keyLen]; 83 if ((c == '=' || c == ':') && !precedingBackslash) 84 { 85 valueStart = keyLen + 1; 86 hasSep = true; 87 break; 88 } 89 else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) 90 { 91 valueStart = keyLen + 1; 92 break; 93 } 94 95 if (c == '\\') 96 precedingBackslash = !precedingBackslash; 97 else 98 precedingBackslash = false; 99 100 keyLen++; 101 } 102 103 while (valueStart < limit) 104 { 105 c = line[valueStart]; 106 if (c != ' ' && c != '\t' && c != '\f') 107 { 108 if (!hasSep && (c == '=' || c == ':')) 109 hasSep = true; 110 else 111 break; 112 } 113 valueStart++; 114 } 115 116 return tuple!("key", "value")(line[0..keyLen], line[valueStart..limit]); 117 } 118 119 auto pairRange = data.splitLines 120 .filter!(l => l.length > 0 && !(l[0] == '#' || l[0] == '!')) 121 .map!(parsePair); 122 123 foreach(Pair p; pairRange) 124 result.set(p.key, convertValue(p.value)); 125 126 return result; 127 } 128 129 130 private PropNode convertValue(string value) 131 { 132 if (value == "false") 133 return PropNode(false); 134 else if (value == "true") 135 return PropNode(true); 136 else if (value == "null") 137 return PropNode(); 138 else 139 { 140 switch (value.front) 141 { 142 case '-': 143 case '0': .. case '9': 144 bool is_long_overflow; 145 bool is_float; 146 auto num = skipNumber(value, is_float, is_long_overflow); 147 if (is_float) 148 return PropNode(to!double(num)); 149 else 150 return PropNode(to!long(num)); 151 default: 152 return PropNode(value); 153 } 154 } 155 } 156 157 /** 158 * Returns the file extension to delermite the type loader 159 */ 160 string[] getExtensions() 161 { 162 return [".properties"]; 163 } 164 } 165 166 167 /** 168 * Parse value 169 * get from vibe.d 170 */ 171 private string skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow) 172 { 173 size_t idx = 0; 174 is_float = false; 175 is_long_overflow = false; 176 ulong int_part = 0; 177 if (s[idx] == '-') idx++; 178 if (s[idx] == '0') idx++; 179 else { 180 enforceProperties(isDigit(s[idx]), "Digit expected at beginning of number."); 181 int_part = s[idx++] - '0'; 182 while( idx < s.length && isDigit(s[idx]) ) 183 { 184 if (!is_long_overflow) 185 { 186 auto dig = s[idx] - '0'; 187 if ((long.max / 10) > int_part || ((long.max / 10) == int_part && (long.max % 10) >= dig)) 188 { 189 int_part *= 10; 190 int_part += dig; 191 } 192 else 193 { 194 is_long_overflow = true; 195 } 196 } 197 idx++; 198 } 199 } 200 201 if( idx < s.length && s[idx] == '.' ){ 202 idx++; 203 is_float = true; 204 while( idx < s.length && isDigit(s[idx]) ) idx++; 205 } 206 207 if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){ 208 idx++; 209 is_float = true; 210 if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++; 211 enforceProperties( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]); 212 idx++; 213 while( idx < s.length && isDigit(s[idx]) ) idx++; 214 } 215 216 string ret = s[0 .. idx]; 217 s = s[idx .. $]; 218 return ret; 219 } 220 221 222 private bool isDigit(dchar ch) @safe nothrow pure 223 { 224 return ch >= '0' && ch <= '9'; 225 } 226 227 228 private void enforceProperties(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "Properties exception") 229 { 230 enforceEx!PropertiesParseException(cond, message, file, line); 231 } 232 233 234 class PropertiesParseException : Exception 235 { 236 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 237 { 238 super(msg, file, line, next); 239 } 240 } 241