1 2 // Copyright Ferdinand Majerech 2011. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 /** 8 * Implements a class that resolves YAML tags. This can be used to implicitly 9 * resolve tags for custom data types, removing the need to explicitly 10 * specify tags in YAML. A tutorial can be found 11 * $(LINK2 ../tutorials/custom_types.html, here). 12 * 13 * Code based on $(LINK2 http://www.pyyaml.org, PyYAML). 14 */ 15 module dyaml.resolver; 16 17 18 import std.conv; 19 import std.regex; 20 import std.stdio; 21 import std.typecons; 22 import std.utf; 23 24 import dyaml.node; 25 import dyaml.exception; 26 import dyaml.tag; 27 28 29 /** 30 * Resolves YAML tags (data types). 31 * 32 * Can be used to implicitly resolve custom data types of scalar values. 33 */ 34 final class Resolver 35 { 36 private: 37 // Default tag to use for scalars. 38 Tag defaultScalarTag_; 39 // Default tag to use for sequences. 40 Tag defaultSequenceTag_; 41 // Default tag to use for mappings. 42 Tag defaultMappingTag_; 43 44 /* 45 * Arrays of scalar resolver tuples indexed by starting character of a scalar. 46 * 47 * Each tuple stores regular expression the scalar must match, 48 * and tag to assign to it if it matches. 49 */ 50 Tuple!(Tag, Regex!char)[][dchar] yamlImplicitResolvers_; 51 52 public: 53 @disable bool opEquals(ref Resolver); 54 @disable int opCmp(ref Resolver); 55 56 /** 57 * Construct a Resolver. 58 * 59 * If you don't want to implicitly resolve default YAML tags/data types, 60 * you can use defaultImplicitResolvers to disable default resolvers. 61 * 62 * Params: defaultImplicitResolvers = Use default YAML implicit resolvers? 63 */ 64 this(Flag!"useDefaultImplicitResolvers" defaultImplicitResolvers = Yes.useDefaultImplicitResolvers) 65 @safe 66 { 67 defaultScalarTag_ = Tag("tag:yaml.org,2002:str"); 68 defaultSequenceTag_ = Tag("tag:yaml.org,2002:seq"); 69 defaultMappingTag_ = Tag("tag:yaml.org,2002:map"); 70 if(defaultImplicitResolvers){addImplicitResolvers();} 71 } 72 73 ///Destroy the Resolver. 74 pure @safe nothrow ~this() 75 { 76 yamlImplicitResolvers_.destroy(); 77 yamlImplicitResolvers_ = null; 78 } 79 80 /** 81 * Add an implicit scalar resolver. 82 * 83 * If a scalar matches regexp and starts with any character in first, 84 * its _tag is set to tag. If it matches more than one resolver _regexp 85 * resolvers added _first override ones added later. Default resolvers 86 * override any user specified resolvers, but they can be disabled in 87 * Resolver constructor. 88 * 89 * If a scalar is not resolved to anything, it is assigned the default 90 * YAML _tag for strings. 91 * 92 * Params: tag = Tag to resolve to. 93 * regexp = Regular expression the scalar must match to have this _tag. 94 * first = String of possible starting characters of the scalar. 95 * 96 * Examples: 97 * 98 * Resolve scalars starting with 'A' to !_tag : 99 * -------------------- 100 * import std.regex; 101 * 102 * import dyaml.all; 103 * 104 * void main() 105 * { 106 * auto loader = Loader("file.txt"); 107 * auto resolver = new Resolver(); 108 * resolver.addImplicitResolver("!tag", std.regex.regex("A.*"), "A"); 109 * loader.resolver = resolver; 110 * 111 * //Note that we have no constructor from tag "!tag", so we can't 112 * //actually load anything that resolves to this tag. 113 * //See Constructor API documentation and tutorial for more information. 114 * 115 * auto node = loader.load(); 116 * } 117 * -------------------- 118 */ 119 void addImplicitResolver(string tag, Regex!char regexp, string first) 120 pure @safe 121 { 122 foreach(const dchar c; first) 123 { 124 if((c in yamlImplicitResolvers_) is null) 125 { 126 yamlImplicitResolvers_[c] = []; 127 } 128 yamlImplicitResolvers_[c] ~= tuple(Tag(tag), regexp); 129 } 130 } 131 132 package: 133 /* 134 * Resolve tag of a node. 135 * 136 * Params: kind = Type of the node. 137 * tag = Explicit tag of the node, if any. 138 * value = Value of the node, if any. 139 * implicit = Should the node be implicitly resolved? 140 * 141 * If the tag is already specified and not non-specific, that tag will 142 * be returned. 143 * 144 * Returns: Resolved tag. 145 */ 146 Tag resolve(const NodeID kind, const Tag tag, const string value, 147 const bool implicit) @safe 148 { 149 if(!tag.isNull() && tag.get() != "!"){return tag;} 150 151 if(kind == NodeID.Scalar) 152 { 153 if(!implicit){return defaultScalarTag_;} 154 155 //Get the first char of the value. 156 size_t dummy; 157 const dchar first = value.length == 0 ? '\0' : decode(value, dummy); 158 159 auto resolvers = (first in yamlImplicitResolvers_) is null ? 160 [] : yamlImplicitResolvers_[first]; 161 162 //If regexp matches, return tag. 163 foreach(resolver; resolvers) if(!(match(value, resolver[1]).empty)) 164 { 165 return resolver[0]; 166 } 167 return defaultScalarTag_; 168 } 169 else if(kind == NodeID.Sequence){return defaultSequenceTag_;} 170 else if(kind == NodeID.Mapping) {return defaultMappingTag_;} 171 assert(false, "This line of code should never be reached"); 172 } 173 unittest 174 { 175 writeln("D:YAML Resolver unittest"); 176 177 auto resolver = new Resolver(); 178 179 bool tagMatch(string tag, string[] values) 180 { 181 Tag expected = Tag(tag); 182 foreach(value; values) 183 { 184 Tag resolved = resolver.resolve(NodeID.Scalar, Tag(), value, true); 185 if(expected != resolved) 186 { 187 return false; 188 } 189 } 190 return true; 191 } 192 193 assert(tagMatch("tag:yaml.org,2002:bool", 194 ["yes", "NO", "True", "on"])); 195 assert(tagMatch("tag:yaml.org,2002:float", 196 ["6.8523015e+5", "685.230_15e+03", "685_230.15", 197 "190:20:30.15", "-.inf", ".NaN"])); 198 assert(tagMatch("tag:yaml.org,2002:int", 199 ["685230", "+685_230", "02472256", "0x_0A_74_AE", 200 "0b1010_0111_0100_1010_1110", "190:20:30"])); 201 assert(tagMatch("tag:yaml.org,2002:merge", ["<<"])); 202 assert(tagMatch("tag:yaml.org,2002:null", ["~", "null", ""])); 203 assert(tagMatch("tag:yaml.org,2002:str", 204 ["abcd", "9a8b", "9.1adsf"])); 205 assert(tagMatch("tag:yaml.org,2002:timestamp", 206 ["2001-12-15T02:59:43.1Z", 207 "2001-12-14t21:59:43.10-05:00", 208 "2001-12-14 21:59:43.10 -5", 209 "2001-12-15 2:59:43.10", 210 "2002-12-14"])); 211 assert(tagMatch("tag:yaml.org,2002:value", ["="])); 212 assert(tagMatch("tag:yaml.org,2002:yaml", ["!", "&", "*"])); 213 } 214 215 ///Return default scalar tag. 216 @property Tag defaultScalarTag() const pure @safe nothrow {return defaultScalarTag_;} 217 218 ///Return default sequence tag. 219 @property Tag defaultSequenceTag() const pure @safe nothrow {return defaultSequenceTag_;} 220 221 ///Return default mapping tag. 222 @property Tag defaultMappingTag() const pure @safe nothrow {return defaultMappingTag_;} 223 224 private: 225 // Add default implicit resolvers. 226 void addImplicitResolvers() @safe 227 { 228 addImplicitResolver("tag:yaml.org,2002:bool", 229 regex(r"^(?:yes|Yes|YES|no|No|NO|true|True|TRUE" ~ 230 "|false|False|FALSE|on|On|ON|off|Off|OFF)$"), 231 "yYnNtTfFoO"); 232 addImplicitResolver("tag:yaml.org,2002:float", 233 regex(r"^(?:[-+]?([0-9][0-9_]*)\\.[0-9_]*" ~ 234 "(?:[eE][-+][0-9]+)?|[-+]?(?:[0-9][0-9_]" ~ 235 "*)?\\.[0-9_]+(?:[eE][-+][0-9]+)?|[-+]?" ~ 236 "[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]" ~ 237 "*|[-+]?\\.(?:inf|Inf|INF)|\\." ~ 238 "(?:nan|NaN|NAN))$"), 239 "-+0123456789."); 240 addImplicitResolver("tag:yaml.org,2002:int", 241 regex(r"^(?:[-+]?0b[0-1_]+" ~ 242 "|[-+]?0[0-7_]+" ~ 243 "|[-+]?(?:0|[1-9][0-9_]*)" ~ 244 "|[-+]?0x[0-9a-fA-F_]+" ~ 245 "|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$"), 246 "-+0123456789"); 247 addImplicitResolver("tag:yaml.org,2002:merge", regex(r"^<<$"), "<"); 248 addImplicitResolver("tag:yaml.org,2002:null", 249 regex(r"^$|^(?:~|null|Null|NULL)$"), "~nN\0"); 250 addImplicitResolver("tag:yaml.org,2002:timestamp", 251 regex(r"^[0-9][0-9][0-9][0-9]-[0-9][0-9]-" ~ 252 "[0-9][0-9]|[0-9][0-9][0-9][0-9]-[0-9]" ~ 253 "[0-9]?-[0-9][0-9]?[Tt]|[ \t]+[0-9]" ~ 254 "[0-9]?:[0-9][0-9]:[0-9][0-9]" ~ 255 "(?:\\.[0-9]*)?(?:[ \t]*Z|[-+][0-9]" ~ 256 "[0-9]?(?::[0-9][0-9])?)?$"), 257 "0123456789"); 258 addImplicitResolver("tag:yaml.org,2002:value", regex(r"^=$"), "="); 259 260 261 //The following resolver is only for documentation purposes. It cannot work 262 //because plain scalars cannot start with '!', '&', or '*'. 263 addImplicitResolver("tag:yaml.org,2002:yaml", regex(r"^(?:!|&|\*)$"), "!&*"); 264 } 265 }