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 |