| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /// This file contains the node classes for the internal representations of YAML | |
| 6 /// documents. These nodes are used for both the serialization tree and the | |
| 7 /// representation graph. | |
| 8 library yaml.model; | |
| 9 | |
| 10 import 'package:source_span/source_span.dart'; | |
| 11 | |
| 12 import 'equality.dart'; | |
| 13 import 'parser.dart'; | |
| 14 import 'visitor.dart'; | |
| 15 import 'yaml_exception.dart'; | |
| 16 | |
| 17 /// The prefix for tag types defined by the YAML spec. | |
| 18 const _YAML_URI_PREFIX = "tag:yaml.org,2002:"; | |
| 19 | |
| 20 /// A tag that indicates the type of a YAML node. | |
| 21 class Tag { | |
| 22 /// The name of the tag, either a URI or a local tag beginning with "!". | |
| 23 final String name; | |
| 24 | |
| 25 /// The kind of the tag. | |
| 26 final TagKind kind; | |
| 27 | |
| 28 /// Returns the standard YAML tag URI for [type]. | |
| 29 static String yaml(String type) => "tag:yaml.org,2002:$type"; | |
| 30 | |
| 31 const Tag(this.name, this.kind); | |
| 32 | |
| 33 const Tag.scalar(String name) | |
| 34 : this(name, TagKind.SCALAR); | |
| 35 | |
| 36 const Tag.sequence(String name) | |
| 37 : this(name, TagKind.SEQUENCE); | |
| 38 | |
| 39 const Tag.mapping(String name) | |
| 40 : this(name, TagKind.MAPPING); | |
| 41 | |
| 42 /// Two tags are equal if their URIs are equal. | |
| 43 operator ==(other) { | |
| 44 if (other is! Tag) return false; | |
| 45 return name == other.name; | |
| 46 } | |
| 47 | |
| 48 String toString() { | |
| 49 if (name.startsWith(_YAML_URI_PREFIX)) { | |
| 50 return '!!${name.substring(_YAML_URI_PREFIX.length)}'; | |
| 51 } else { | |
| 52 return '!<$name>'; | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 int get hashCode => name.hashCode; | |
| 57 } | |
| 58 | |
| 59 /// An enum for kinds of tags. | |
| 60 class TagKind { | |
| 61 /// A tag indicating that the value is a scalar. | |
| 62 static const SCALAR = const TagKind._("scalar"); | |
| 63 | |
| 64 /// A tag indicating that the value is a sequence. | |
| 65 static const SEQUENCE = const TagKind._("sequence"); | |
| 66 | |
| 67 /// A tag indicating that the value is a mapping. | |
| 68 static const MAPPING = const TagKind._("mapping"); | |
| 69 | |
| 70 final String name; | |
| 71 | |
| 72 const TagKind._(this.name); | |
| 73 | |
| 74 String toString() => name; | |
| 75 } | |
| 76 | |
| 77 /// The abstract class for YAML nodes. | |
| 78 abstract class Node { | |
| 79 /// Every YAML node has a tag that describes its type. | |
| 80 Tag tag; | |
| 81 | |
| 82 /// Any YAML node can have an anchor associated with it. | |
| 83 String anchor; | |
| 84 | |
| 85 /// The source span for this node. | |
| 86 SourceSpan span; | |
| 87 | |
| 88 Node(this.tag, this.span, [this.anchor]); | |
| 89 | |
| 90 bool operator ==(other) { | |
| 91 if (other is! Node) return false; | |
| 92 return tag == other.tag; | |
| 93 } | |
| 94 | |
| 95 int get hashCode => tag.hashCode ^ anchor.hashCode; | |
| 96 | |
| 97 visit(Visitor v); | |
| 98 } | |
| 99 | |
| 100 /// A sequence node represents an ordered list of nodes. | |
| 101 class SequenceNode extends Node { | |
| 102 /// The nodes in the sequence. | |
| 103 List<Node> content; | |
| 104 | |
| 105 SequenceNode(String tagName, this.content, SourceSpan span) | |
| 106 : super(new Tag.sequence(tagName), span); | |
| 107 | |
| 108 /// Two sequences are equal if their tags and contents are equal. | |
| 109 bool operator ==(other) { | |
| 110 // Should be super != other; bug 2554 | |
| 111 if (!(super == other) || other is! SequenceNode) return false; | |
| 112 if (content.length != other.content.length) return false; | |
| 113 for (var i = 0; i < content.length; i++) { | |
| 114 if (content[i] != other.content[i]) return false; | |
| 115 } | |
| 116 return true; | |
| 117 } | |
| 118 | |
| 119 String toString() => '$tag [${content.map((e) => '$e').join(', ')}]'; | |
| 120 | |
| 121 int get hashCode => super.hashCode ^ deepHashCode(content); | |
| 122 | |
| 123 visit(Visitor v) => v.visitSequence(this); | |
| 124 } | |
| 125 | |
| 126 /// An alias node is a reference to an anchor. | |
| 127 class AliasNode extends Node { | |
| 128 AliasNode(String anchor, SourceSpan span) | |
| 129 : super(new Tag.scalar(Tag.yaml("str")), span, anchor); | |
| 130 | |
| 131 visit(Visitor v) => v.visitAlias(this); | |
| 132 } | |
| 133 | |
| 134 /// A scalar node represents all YAML nodes that have a single value. | |
| 135 class ScalarNode extends Node { | |
| 136 /// The string value of the scalar node, if it was created by the parser. | |
| 137 final String _content; | |
| 138 | |
| 139 /// The Dart value of the scalar node, if it was created by the composer. | |
| 140 final value; | |
| 141 | |
| 142 /// Creates a new Scalar node. | |
| 143 /// | |
| 144 /// Exactly one of [content] and [value] should be specified. Content should | |
| 145 /// be specified for a newly-parsed scalar that hasn't yet been composed. | |
| 146 /// Value should be specified for a composed scalar, although `null` is a | |
| 147 /// valid value. | |
| 148 ScalarNode(String tagName, SourceSpan span, {String content, this.value}) | |
| 149 : _content = content, | |
| 150 super(new Tag.scalar(tagName), span); | |
| 151 | |
| 152 /// Two scalars are equal if their string representations are equal. | |
| 153 bool operator ==(other) { | |
| 154 // Should be super != other; bug 2554 | |
| 155 if (!(super == other) || other is! ScalarNode) return false; | |
| 156 return content == other.content; | |
| 157 } | |
| 158 | |
| 159 /// Returns the string representation of the scalar. After composition, this | |
| 160 /// is equal to the canonical serialization of the value of the scalar. | |
| 161 String get content => _content != null ? _content : canonicalContent; | |
| 162 | |
| 163 /// Returns the canonical serialization of the value of the scalar. If the | |
| 164 /// value isn't given, the result of this will be "null". | |
| 165 String get canonicalContent { | |
| 166 if (value == null || value is bool || value is int) return '$value'; | |
| 167 | |
| 168 if (value is num) { | |
| 169 // 20 is the maximum value for this argument, which we use since YAML | |
| 170 // doesn't specify a maximum. | |
| 171 return value.toStringAsExponential(20). | |
| 172 replaceFirst(new RegExp("0+e"), "e"); | |
| 173 } | |
| 174 | |
| 175 if (value is String) { | |
| 176 // TODO(nweiz): This could be faster if we used a RegExp to check for | |
| 177 // special characters and short-circuited if they didn't exist. | |
| 178 | |
| 179 var escapedValue = value.codeUnits.map((c) { | |
| 180 switch (c) { | |
| 181 case Parser.TAB: return "\\t"; | |
| 182 case Parser.LF: return "\\n"; | |
| 183 case Parser.CR: return "\\r"; | |
| 184 case Parser.DOUBLE_QUOTE: return '\\"'; | |
| 185 case Parser.NULL: return "\\0"; | |
| 186 case Parser.BELL: return "\\a"; | |
| 187 case Parser.BACKSPACE: return "\\b"; | |
| 188 case Parser.VERTICAL_TAB: return "\\v"; | |
| 189 case Parser.FORM_FEED: return "\\f"; | |
| 190 case Parser.ESCAPE: return "\\e"; | |
| 191 case Parser.BACKSLASH: return "\\\\"; | |
| 192 case Parser.NEL: return "\\N"; | |
| 193 case Parser.NBSP: return "\\_"; | |
| 194 case Parser.LINE_SEPARATOR: return "\\L"; | |
| 195 case Parser.PARAGRAPH_SEPARATOR: return "\\P"; | |
| 196 default: | |
| 197 if (c < 0x20 || (c >= 0x7f && c < 0x100)) { | |
| 198 return "\\x${zeroPad(c.toRadixString(16).toUpperCase(), 2)}"; | |
| 199 } else if (c >= 0x100 && c < 0x10000) { | |
| 200 return "\\u${zeroPad(c.toRadixString(16).toUpperCase(), 4)}"; | |
| 201 } else if (c >= 0x10000) { | |
| 202 return "\\u${zeroPad(c.toRadixString(16).toUpperCase(), 8)}"; | |
| 203 } else { | |
| 204 return new String.fromCharCodes([c]); | |
| 205 } | |
| 206 } | |
| 207 }); | |
| 208 return '"${escapedValue.join()}"'; | |
| 209 } | |
| 210 | |
| 211 throw new YamlException('Unknown scalar value.', span); | |
| 212 } | |
| 213 | |
| 214 String toString() => '$tag "$content"'; | |
| 215 | |
| 216 /// Left-pads [str] with zeros so that it's at least [length] characters | |
| 217 /// long. | |
| 218 String zeroPad(String str, int length) { | |
| 219 assert(length >= str.length); | |
| 220 var prefix = new List.filled(length - str.length, '0'); | |
| 221 return '${prefix.join()}$str'; | |
| 222 } | |
| 223 | |
| 224 int get hashCode => super.hashCode ^ content.hashCode; | |
| 225 | |
| 226 visit(Visitor v) => v.visitScalar(this); | |
| 227 } | |
| 228 | |
| 229 /// A mapping node represents an unordered map of nodes to nodes. | |
| 230 class MappingNode extends Node { | |
| 231 /// The node map. | |
| 232 Map<Node, Node> content; | |
| 233 | |
| 234 MappingNode(String tagName, this.content, SourceSpan span) | |
| 235 : super(new Tag.mapping(tagName), span); | |
| 236 | |
| 237 /// Two mappings are equal if their tags and contents are equal. | |
| 238 bool operator ==(other) { | |
| 239 // Should be super != other; bug 2554 | |
| 240 if (!(super == other) || other is! MappingNode) return false; | |
| 241 if (content.length != other.content.length) return false; | |
| 242 for (var key in content.keys) { | |
| 243 if (!other.content.containsKey(key)) return false; | |
| 244 if (content[key] != other.content[key]) return false; | |
| 245 } | |
| 246 return true; | |
| 247 } | |
| 248 | |
| 249 String toString() { | |
| 250 var strContent = content.keys | |
| 251 .map((k) => '${k}: ${content[k]}') | |
| 252 .join(', '); | |
| 253 return '$tag {$strContent}'; | |
| 254 } | |
| 255 | |
| 256 int get hashCode => super.hashCode ^ deepHashCode(content); | |
| 257 | |
| 258 visit(Visitor v) => v.visitMapping(this); | |
| 259 } | |
| OLD | NEW |