scratch – Diff between revs 75 and 125

Subversion Repositories:
Rev:
Only display areas with differencesIgnore whitespace
Rev 75 Rev 125
1   1  
2 Inline = require './Inline' 2 Inline = require './Inline'
3 Pattern = require './Pattern' 3 Pattern = require './Pattern'
4 Utils = require './Utils' 4 Utils = require './Utils'
5 ParseException = require './Exception/ParseException' 5 ParseException = require './Exception/ParseException'
-   6 ParseMore = require './Exception/ParseMore'
6   7  
7 # Parser parses YAML strings to convert them to JavaScript objects. 8 # Parser parses YAML strings to convert them to JavaScript objects.
8 # 9 #
9 class Parser 10 class Parser
10   11  
11 # Pre-compiled patterns 12 # Pre-compiled patterns
12 # 13 #
13 PATTERN_FOLDED_SCALAR_ALL: new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$' 14 PATTERN_FOLDED_SCALAR_ALL: new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
14 PATTERN_FOLDED_SCALAR_END: new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$' 15 PATTERN_FOLDED_SCALAR_END: new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
15 PATTERN_SEQUENCE_ITEM: new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$' 16 PATTERN_SEQUENCE_ITEM: new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$'
16 PATTERN_ANCHOR_VALUE: new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)' 17 PATTERN_ANCHOR_VALUE: new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)'
17 PATTERN_COMPACT_NOTATION: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$' 18 PATTERN_COMPACT_NOTATION: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
18 PATTERN_MAPPING_ITEM: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$' 19 PATTERN_MAPPING_ITEM: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
19 PATTERN_DECIMAL: new Pattern '\\d+' 20 PATTERN_DECIMAL: new Pattern '\\d+'
20 PATTERN_INDENT_SPACES: new Pattern '^ +' 21 PATTERN_INDENT_SPACES: new Pattern '^ +'
21 PATTERN_TRAILING_LINES: new Pattern '(\n*)$' 22 PATTERN_TRAILING_LINES: new Pattern '(\n*)$'
22 PATTERN_YAML_HEADER: new Pattern '^\\%YAML[: ][\\d\\.]+.*\n' 23 PATTERN_YAML_HEADER: new Pattern '^\\%YAML[: ][\\d\\.]+.*\n', 'm'
23 PATTERN_LEADING_COMMENTS: new Pattern '^(\\#.*?\n)+' 24 PATTERN_LEADING_COMMENTS: new Pattern '^(\\#.*?\n)+', 'm'
24 PATTERN_DOCUMENT_MARKER_START: new Pattern '^\\-\\-\\-.*?\n' 25 PATTERN_DOCUMENT_MARKER_START: new Pattern '^\\-\\-\\-.*?\n', 'm'
25 PATTERN_DOCUMENT_MARKER_END: new Pattern '^\\.\\.\\.\\s*$' 26 PATTERN_DOCUMENT_MARKER_END: new Pattern '^\\.\\.\\.\\s*$', 'm'
26 PATTERN_FOLDED_SCALAR_BY_INDENTATION: {} 27 PATTERN_FOLDED_SCALAR_BY_INDENTATION: {}
27   28  
28 # Context types 29 # Context types
29 # 30 #
30 CONTEXT_NONE: 0 31 CONTEXT_NONE: 0
31 CONTEXT_SEQUENCE: 1 32 CONTEXT_SEQUENCE: 1
32 CONTEXT_MAPPING: 2 33 CONTEXT_MAPPING: 2
33   34  
34   35  
35 # Constructor 36 # Constructor
36 # 37 #
37 # @param [Integer] offset The offset of YAML document (used for line numbers in error messages) 38 # @param [Integer] offset The offset of YAML document (used for line numbers in error messages)
38 # 39 #
39 constructor: (@offset = 0) -> 40 constructor: (@offset = 0) ->
40 @lines = [] 41 @lines = []
41 @currentLineNb = -1 42 @currentLineNb = -1
42 @currentLine = '' 43 @currentLine = ''
43 @refs = {} 44 @refs = {}
44   45  
45   46  
46 # Parses a YAML string to a JavaScript value. 47 # Parses a YAML string to a JavaScript value.
47 # 48 #
48 # @param [String] value A YAML string 49 # @param [String] value A YAML string
49 # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise 50 # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
50 # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise 51 # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise
51 # 52 #
52 # @return [Object] A JavaScript value 53 # @return [Object] A JavaScript value
53 # 54 #
54 # @throw [ParseException] If the YAML is not valid 55 # @throw [ParseException] If the YAML is not valid
55 # 56 #
56 parse: (value, exceptionOnInvalidType = false, objectDecoder = null) -> 57 parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
57 @currentLineNb = -1 58 @currentLineNb = -1
58 @currentLine = '' 59 @currentLine = ''
59 @lines = @cleanup(value).split "\n" 60 @lines = @cleanup(value).split "\n"
60   61  
61 data = null 62 data = null
62 context = @CONTEXT_NONE 63 context = @CONTEXT_NONE
63 allowOverwrite = false 64 allowOverwrite = false
64 while @moveToNextLine() 65 while @moveToNextLine()
65 if @isCurrentLineEmpty() 66 if @isCurrentLineEmpty()
66 continue 67 continue
67   68  
68 # Tab? 69 # Tab?
69 if "\t" is @currentLine[0] 70 if "\t" is @currentLine[0]
70 throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine 71 throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine
71   72  
72 isRef = mergeNode = false 73 isRef = mergeNode = false
73 if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine 74 if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine
74 if @CONTEXT_MAPPING is context 75 if @CONTEXT_MAPPING is context
75 throw new ParseException 'You cannot define a sequence item when in a mapping' 76 throw new ParseException 'You cannot define a sequence item when in a mapping'
76 context = @CONTEXT_SEQUENCE 77 context = @CONTEXT_SEQUENCE
77 data ?= [] 78 data ?= []
78   79  
79 if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value 80 if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
80 isRef = matches.ref 81 isRef = matches.ref
81 values.value = matches.value 82 values.value = matches.value
82   83  
83 # Array 84 # Array
84 if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0 85 if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
85 if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection() 86 if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection()
86 c = @getRealCurrentLineNb() + 1 87 c = @getRealCurrentLineNb() + 1
87 parser = new Parser c 88 parser = new Parser c
88 parser.refs = @refs 89 parser.refs = @refs
89 data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder) 90 data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder)
90 else 91 else
91 data.push null 92 data.push null
92   93  
93 else 94 else
94 if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value 95 if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value
95   96  
96 # This is a compact notation element, add to next block and parse 97 # This is a compact notation element, add to next block and parse
97 c = @getRealCurrentLineNb() 98 c = @getRealCurrentLineNb()
98 parser = new Parser c 99 parser = new Parser c
99 parser.refs = @refs 100 parser.refs = @refs
100   101  
101 block = values.value 102 block = values.value
102 indent = @getCurrentLineIndentation() 103 indent = @getCurrentLineIndentation()
103 if @isNextLineIndented(false) 104 if @isNextLineIndented(false)
104 block += "\n"+@getNextEmbedBlock(indent + values.leadspaces.length + 1, true) 105 block += "\n"+@getNextEmbedBlock(indent + values.leadspaces.length + 1, true)
105   106  
106 data.push parser.parse block, exceptionOnInvalidType, objectDecoder 107 data.push parser.parse block, exceptionOnInvalidType, objectDecoder
107   108  
108 else 109 else
109 data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder 110 data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder
110   111  
111 else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1 112 else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1
112 if @CONTEXT_SEQUENCE is context 113 if @CONTEXT_SEQUENCE is context
113 throw new ParseException 'You cannot define a mapping item when in a sequence' 114 throw new ParseException 'You cannot define a mapping item when in a sequence'
114 context = @CONTEXT_MAPPING 115 context = @CONTEXT_MAPPING
115 data ?= {} 116 data ?= {}
116   117  
117 # Force correct settings 118 # Force correct settings
118 Inline.configure exceptionOnInvalidType, objectDecoder 119 Inline.configure exceptionOnInvalidType, objectDecoder
119 try 120 try
120 key = Inline.parseScalar values.key 121 key = Inline.parseScalar values.key
121 catch e 122 catch e
122 e.parsedLine = @getRealCurrentLineNb() + 1 123 e.parsedLine = @getRealCurrentLineNb() + 1
123 e.snippet = @currentLine 124 e.snippet = @currentLine
124   125  
125 throw e 126 throw e
126   127  
127 if '<<' is key 128 if '<<' is key
128 mergeNode = true 129 mergeNode = true
129 allowOverwrite = true 130 allowOverwrite = true
130 if values.value?.indexOf('*') is 0 131 if values.value?.indexOf('*') is 0
131 refName = values.value[1..] 132 refName = values.value[1..]
132 unless @refs[refName]? 133 unless @refs[refName]?
133 throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine 134 throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine
134   135  
135 refValue = @refs[refName] 136 refValue = @refs[refName]
136   137  
137 if typeof refValue isnt 'object' 138 if typeof refValue isnt 'object'
138 throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine 139 throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
139   140  
140 if refValue instanceof Array 141 if refValue instanceof Array
141 # Merge array with object 142 # Merge array with object
142 for value, i in refValue 143 for value, i in refValue
143 data[String(i)] ?= value 144 data[String(i)] ?= value
144 else 145 else
145 # Merge objects 146 # Merge objects
146 for key, value of refValue 147 for key, value of refValue
147 data[key] ?= value 148 data[key] ?= value
148   149  
149 else 150 else
150 if values.value? and values.value isnt '' 151 if values.value? and values.value isnt ''
151 value = values.value 152 value = values.value
152 else 153 else
153 value = @getNextEmbedBlock() 154 value = @getNextEmbedBlock()
154   155  
155 c = @getRealCurrentLineNb() + 1 156 c = @getRealCurrentLineNb() + 1
156 parser = new Parser c 157 parser = new Parser c
157 parser.refs = @refs 158 parser.refs = @refs
158 parsed = parser.parse value, exceptionOnInvalidType 159 parsed = parser.parse value, exceptionOnInvalidType
159   160  
160 unless typeof parsed is 'object' 161 unless typeof parsed is 'object'
161 throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine 162 throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
162   163  
163 if parsed instanceof Array 164 if parsed instanceof Array
164 # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes 165 # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
165 # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier 166 # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
166 # in the sequence override keys specified in later mapping nodes. 167 # in the sequence override keys specified in later mapping nodes.
167 for parsedItem in parsed 168 for parsedItem in parsed
168 unless typeof parsedItem is 'object' 169 unless typeof parsedItem is 'object'
169 throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem 170 throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem
170   171  
171 if parsedItem instanceof Array 172 if parsedItem instanceof Array
172 # Merge array with object 173 # Merge array with object
173 for value, i in parsedItem 174 for value, i in parsedItem
174 k = String(i) 175 k = String(i)
175 unless data.hasOwnProperty(k) 176 unless data.hasOwnProperty(k)
176 data[k] = value 177 data[k] = value
177 else 178 else
178 # Merge objects 179 # Merge objects
179 for key, value of parsedItem 180 for key, value of parsedItem
180 unless data.hasOwnProperty(key) 181 unless data.hasOwnProperty(key)
181 data[key] = value 182 data[key] = value
182   183  
183 else 184 else
184 # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the 185 # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
185 # current mapping, unless the key already exists in it. 186 # current mapping, unless the key already exists in it.
186 for key, value of parsed 187 for key, value of parsed
187 unless data.hasOwnProperty(key) 188 unless data.hasOwnProperty(key)
188 data[key] = value 189 data[key] = value
189   190  
190 else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value 191 else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
191 isRef = matches.ref 192 isRef = matches.ref
192 values.value = matches.value 193 values.value = matches.value
193   194  
194   195  
195 if mergeNode 196 if mergeNode
196 # Merge keys 197 # Merge keys
197 else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0 198 else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
198 # Hash 199 # Hash
199 # if next line is less indented or equal, then it means that the current value is null 200 # if next line is less indented or equal, then it means that the current value is null
200 if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection()) 201 if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection())
201 # Spec: Keys MUST be unique; first one wins. 202 # Spec: Keys MUST be unique; first one wins.
202 # But overwriting is allowed when a merge node is used in current block. 203 # But overwriting is allowed when a merge node is used in current block.
203 if allowOverwrite or data[key] is undefined 204 if allowOverwrite or data[key] is undefined
204 data[key] = null 205 data[key] = null
205   206  
206 else 207 else
207 c = @getRealCurrentLineNb() + 1 208 c = @getRealCurrentLineNb() + 1
208 parser = new Parser c 209 parser = new Parser c
209 parser.refs = @refs 210 parser.refs = @refs
210 val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder 211 val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder
211   212  
212 # Spec: Keys MUST be unique; first one wins. 213 # Spec: Keys MUST be unique; first one wins.
213 # But overwriting is allowed when a merge node is used in current block. 214 # But overwriting is allowed when a merge node is used in current block.
214 if allowOverwrite or data[key] is undefined 215 if allowOverwrite or data[key] is undefined
215 data[key] = val 216 data[key] = val
216   217  
217 else 218 else
218 val = @parseValue values.value, exceptionOnInvalidType, objectDecoder 219 val = @parseValue values.value, exceptionOnInvalidType, objectDecoder
219   220  
220 # Spec: Keys MUST be unique; first one wins. 221 # Spec: Keys MUST be unique; first one wins.
221 # But overwriting is allowed when a merge node is used in current block. 222 # But overwriting is allowed when a merge node is used in current block.
222 if allowOverwrite or data[key] is undefined 223 if allowOverwrite or data[key] is undefined
223 data[key] = val 224 data[key] = val
224   225  
225 else 226 else
226 # 1-liner optionally followed by newline 227 # 1-liner optionally followed by newline
227 lineCount = @lines.length 228 lineCount = @lines.length
228 if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1])) 229 if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1]))
229 try 230 try
230 value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder 231 value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder
231 catch e 232 catch e
232 e.parsedLine = @getRealCurrentLineNb() + 1 233 e.parsedLine = @getRealCurrentLineNb() + 1
233 e.snippet = @currentLine 234 e.snippet = @currentLine
234   235  
235 throw e 236 throw e
236   237  
237 if typeof value is 'object' 238 if typeof value is 'object'
238 if value instanceof Array 239 if value instanceof Array
239 first = value[0] 240 first = value[0]
240 else 241 else
241 for key of value 242 for key of value
242 first = value[key] 243 first = value[key]
243 break 244 break
244   245  
245 if typeof first is 'string' and first.indexOf('*') is 0 246 if typeof first is 'string' and first.indexOf('*') is 0
246 data = [] 247 data = []
247 for alias in value 248 for alias in value
248 data.push @refs[alias[1..]] 249 data.push @refs[alias[1..]]
249 value = data 250 value = data
250   251  
251 return value 252 return value
252   253  
253 else if Utils.ltrim(value).charAt(0) in ['[', '{'] 254 else if Utils.ltrim(value).charAt(0) in ['[', '{']
254 try 255 try
255 return Inline.parse value, exceptionOnInvalidType, objectDecoder 256 return Inline.parse value, exceptionOnInvalidType, objectDecoder
256 catch e 257 catch e
257 e.parsedLine = @getRealCurrentLineNb() + 1 258 e.parsedLine = @getRealCurrentLineNb() + 1
258 e.snippet = @currentLine 259 e.snippet = @currentLine
259   260  
260 throw e 261 throw e
261   262  
262 throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine 263 throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine
263   264  
264 if isRef 265 if isRef
265 if data instanceof Array 266 if data instanceof Array
266 @refs[isRef] = data[data.length-1] 267 @refs[isRef] = data[data.length-1]
267 else 268 else
268 lastKey = null 269 lastKey = null
269 for key of data 270 for key of data
270 lastKey = key 271 lastKey = key
271 @refs[isRef] = data[lastKey] 272 @refs[isRef] = data[lastKey]
272   273  
273   274  
274 if Utils.isEmpty(data) 275 if Utils.isEmpty(data)
275 return null 276 return null
276 else 277 else
277 return data 278 return data
278   279  
279   280  
280   281  
281 # Returns the current line number (takes the offset into account). 282 # Returns the current line number (takes the offset into account).
282 # 283 #
283 # @return [Integer] The current line number 284 # @return [Integer] The current line number
284 # 285 #
285 getRealCurrentLineNb: -> 286 getRealCurrentLineNb: ->
286 return @currentLineNb + @offset 287 return @currentLineNb + @offset
287   288  
288   289  
289 # Returns the current line indentation. 290 # Returns the current line indentation.
290 # 291 #
291 # @return [Integer] The current line indentation 292 # @return [Integer] The current line indentation
292 # 293 #
293 getCurrentLineIndentation: -> 294 getCurrentLineIndentation: ->
294 return @currentLine.length - Utils.ltrim(@currentLine, ' ').length 295 return @currentLine.length - Utils.ltrim(@currentLine, ' ').length
295   296  
296   297  
297 # Returns the next embed block of YAML. 298 # Returns the next embed block of YAML.
298 # 299 #
299 # @param [Integer] indentation The indent level at which the block is to be read, or null for default 300 # @param [Integer] indentation The indent level at which the block is to be read, or null for default
300 # 301 #
301 # @return [String] A YAML string 302 # @return [String] A YAML string
302 # 303 #
303 # @throw [ParseException] When indentation problem are detected 304 # @throw [ParseException] When indentation problem are detected
304 # 305 #
305 getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) -> 306 getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) ->
306 @moveToNextLine() 307 @moveToNextLine()
307   308  
308 if not indentation? 309 if not indentation?
309 newIndent = @getCurrentLineIndentation() 310 newIndent = @getCurrentLineIndentation()
310   311  
311 unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine 312 unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine
312   313  
313 if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock) 314 if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock)
314 throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine 315 throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
315   316  
316 else 317 else
317 newIndent = indentation 318 newIndent = indentation
318   319  
319   320  
320 data = [@currentLine[newIndent..]] 321 data = [@currentLine[newIndent..]]
321   322  
322 unless includeUnindentedCollection 323 unless includeUnindentedCollection
323 isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine 324 isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine
324   325  
325 # Comments must not be removed inside a string block (ie. after a line ending with "|") 326 # Comments must not be removed inside a string block (ie. after a line ending with "|")
326 # They must not be removed inside a sub-embedded block as well 327 # They must not be removed inside a sub-embedded block as well
327 removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END 328 removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END
328 removeComments = not removeCommentsPattern.test @currentLine 329 removeComments = not removeCommentsPattern.test @currentLine
329   330  
330 while @moveToNextLine() 331 while @moveToNextLine()
331 indent = @getCurrentLineIndentation() 332 indent = @getCurrentLineIndentation()
332   333  
333 if indent is newIndent 334 if indent is newIndent
334 removeComments = not removeCommentsPattern.test @currentLine 335 removeComments = not removeCommentsPattern.test @currentLine
335   -  
336 if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent 336  
337 @moveToPreviousLine() 337 if removeComments and @isCurrentLineComment()
338 break 338 continue
339   339  
340 if @isCurrentLineBlank() 340 if @isCurrentLineBlank()
341 data.push @currentLine[newIndent..] 341 data.push @currentLine[newIndent..]
342 continue 342 continue
343   343  
344 if removeComments and @isCurrentLineComment() 344 if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent
345 if indent is newIndent 345 @moveToPreviousLine()
346 continue 346 break
347   347  
348 if indent >= newIndent 348 if indent >= newIndent
349 data.push @currentLine[newIndent..] 349 data.push @currentLine[newIndent..]
350 else if Utils.ltrim(@currentLine).charAt(0) is '#' 350 else if Utils.ltrim(@currentLine).charAt(0) is '#'
351 # Don't add line with comments 351 # Don't add line with comments
352 else if 0 is indent 352 else if 0 is indent
353 @moveToPreviousLine() 353 @moveToPreviousLine()
354 break 354 break
355 else 355 else
356 throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine 356 throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
357   357  
358   358  
359 return data.join "\n" 359 return data.join "\n"
360   360  
361   361  
362 # Moves the parser to the next line. 362 # Moves the parser to the next line.
363 # 363 #
364 # @return [Boolean] 364 # @return [Boolean]
365 # 365 #
366 moveToNextLine: -> 366 moveToNextLine: ->
367 if @currentLineNb >= @lines.length - 1 367 if @currentLineNb >= @lines.length - 1
368 return false 368 return false
369   369  
370 @currentLine = @lines[++@currentLineNb]; 370 @currentLine = @lines[++@currentLineNb];
371   371  
372 return true 372 return true
373   373  
374   374  
375 # Moves the parser to the previous line. 375 # Moves the parser to the previous line.
376 # 376 #
377 moveToPreviousLine: -> 377 moveToPreviousLine: ->
378 @currentLine = @lines[--@currentLineNb] 378 @currentLine = @lines[--@currentLineNb]
379 return 379 return
380   380  
381   381  
382 # Parses a YAML value. 382 # Parses a YAML value.
383 # 383 #
384 # @param [String] value A YAML value 384 # @param [String] value A YAML value
385 # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types false otherwise 385 # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types false otherwise
386 # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise 386 # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise
387 # 387 #
388 # @return [Object] A JavaScript value 388 # @return [Object] A JavaScript value
389 # 389 #
390 # @throw [ParseException] When reference does not exist 390 # @throw [ParseException] When reference does not exist
391 # 391 #
392 parseValue: (value, exceptionOnInvalidType, objectDecoder) -> 392 parseValue: (value, exceptionOnInvalidType, objectDecoder) ->
393 if 0 is value.indexOf('*') 393 if 0 is value.indexOf('*')
394 pos = value.indexOf '#' 394 pos = value.indexOf '#'
395 if pos isnt -1 395 if pos isnt -1
396 value = value.substr(1, pos-2) 396 value = value.substr(1, pos-2)
397 else 397 else
398 value = value[1..] 398 value = value[1..]
399   399  
400 if @refs[value] is undefined 400 if @refs[value] is undefined
401 throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine 401 throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine
402   402  
403 return @refs[value] 403 return @refs[value]
404   404  
405   405  
406 if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value 406 if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value
407 modifiers = matches.modifiers ? '' 407 modifiers = matches.modifiers ? ''
408   408  
409 foldedIndent = Math.abs(parseInt(modifiers)) 409 foldedIndent = Math.abs(parseInt(modifiers))
410 if isNaN(foldedIndent) then foldedIndent = 0 410 if isNaN(foldedIndent) then foldedIndent = 0
411 val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent 411 val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent
412 if matches.type? 412 if matches.type?
413 # Force correct settings 413 # Force correct settings
414 Inline.configure exceptionOnInvalidType, objectDecoder 414 Inline.configure exceptionOnInvalidType, objectDecoder
415 return Inline.parseScalar matches.type+' '+val 415 return Inline.parseScalar matches.type+' '+val
416 else 416 else
417 return val 417 return val
418   -  
419 try -  
420 return Inline.parse value, exceptionOnInvalidType, objectDecoder -  
421 catch e 418  
422 # Try to parse multiline compact sequence or mapping 419 # Value can be multiline compact sequence or mapping or string
423 if value.charAt(0) in ['[', '{'] and e instanceof ParseException and @isNextLineIndented() 420 if value.charAt(0) in ['[', '{', '"', "'"]
424 value += "\n" + @getNextEmbedBlock() 421 while true
425 try 422 try
426 return Inline.parse value, exceptionOnInvalidType, objectDecoder 423 return Inline.parse value, exceptionOnInvalidType, objectDecoder
427 catch e 424 catch e
-   425 if e instanceof ParseMore and @moveToNextLine()
-   426 value += "\n" + Utils.trim(@currentLine, ' ')
-   427 else
428 e.parsedLine = @getRealCurrentLineNb() + 1 428 e.parsedLine = @getRealCurrentLineNb() + 1
429 e.snippet = @currentLine 429 e.snippet = @currentLine
430   -  
431 throw e 430 throw e
432   -  
433 else 431 else
434 e.parsedLine = @getRealCurrentLineNb() + 1 432 if @isNextLineIndented()
435 e.snippet = @currentLine 433 value += "\n" + @getNextEmbedBlock()
436   -  
437 throw e 434 return Inline.parse value, exceptionOnInvalidType, objectDecoder
438   435  
439 return 436 return
440   437  
441   438  
442 # Parses a folded scalar. 439 # Parses a folded scalar.
443 # 440 #
444 # @param [String] separator The separator that was used to begin this folded scalar (| or >) 441 # @param [String] separator The separator that was used to begin this folded scalar (| or >)
445 # @param [String] indicator The indicator that was used to begin this folded scalar (+ or -) 442 # @param [String] indicator The indicator that was used to begin this folded scalar (+ or -)
446 # @param [Integer] indentation The indentation that was used to begin this folded scalar 443 # @param [Integer] indentation The indentation that was used to begin this folded scalar
447 # 444 #
448 # @return [String] The text value 445 # @return [String] The text value
449 # 446 #
450 parseFoldedScalar: (separator, indicator = '', indentation = 0) -> 447 parseFoldedScalar: (separator, indicator = '', indentation = 0) ->
451 notEOF = @moveToNextLine() 448 notEOF = @moveToNextLine()
452 if not notEOF 449 if not notEOF
453 return '' 450 return ''
454   451  
455 isCurrentLineBlank = @isCurrentLineBlank() 452 isCurrentLineBlank = @isCurrentLineBlank()
456 text = '' 453 text = ''
457   454  
458 # Leading blank lines are consumed before determining indentation 455 # Leading blank lines are consumed before determining indentation
459 while notEOF and isCurrentLineBlank 456 while notEOF and isCurrentLineBlank
460 # newline only if not EOF 457 # newline only if not EOF
461 if notEOF = @moveToNextLine() 458 if notEOF = @moveToNextLine()
462 text += "\n" 459 text += "\n"
463 isCurrentLineBlank = @isCurrentLineBlank() 460 isCurrentLineBlank = @isCurrentLineBlank()
464   461  
465   462  
466 # Determine indentation if not specified 463 # Determine indentation if not specified
467 if 0 is indentation 464 if 0 is indentation
468 if matches = @PATTERN_INDENT_SPACES.exec @currentLine 465 if matches = @PATTERN_INDENT_SPACES.exec @currentLine
469 indentation = matches[0].length 466 indentation = matches[0].length
470   467  
471   468  
472 if indentation > 0 469 if indentation > 0
473 pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] 470 pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation]
474 unless pattern? 471 unless pattern?
475 pattern = new Pattern '^ {'+indentation+'}(.*)$' 472 pattern = new Pattern '^ {'+indentation+'}(.*)$'
476 Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern 473 Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern
477   474  
478 while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine) 475 while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine)
479 if isCurrentLineBlank 476 if isCurrentLineBlank
480 text += @currentLine[indentation..] 477 text += @currentLine[indentation..]
481 else 478 else
482 text += matches[1] 479 text += matches[1]
483   480  
484 # newline only if not EOF 481 # newline only if not EOF
485 if notEOF = @moveToNextLine() 482 if notEOF = @moveToNextLine()
486 text += "\n" 483 text += "\n"
487 isCurrentLineBlank = @isCurrentLineBlank() 484 isCurrentLineBlank = @isCurrentLineBlank()
488   485  
489 else if notEOF 486 else if notEOF
490 text += "\n" 487 text += "\n"
491   488  
492   489  
493 if notEOF 490 if notEOF
494 @moveToPreviousLine() 491 @moveToPreviousLine()
495   492  
496   493  
497 # Remove line breaks of each lines except the empty and more indented ones 494 # Remove line breaks of each lines except the empty and more indented ones
498 if '>' is separator 495 if '>' is separator
499 newText = '' 496 newText = ''
500 for line in text.split "\n" 497 for line in text.split "\n"
501 if line.length is 0 or line.charAt(0) is ' ' 498 if line.length is 0 or line.charAt(0) is ' '
502 newText = Utils.rtrim(newText, ' ') + line + "\n" 499 newText = Utils.rtrim(newText, ' ') + line + "\n"
503 else 500 else
504 newText += line + ' ' 501 newText += line + ' '
505 text = newText 502 text = newText
506   503  
507 if '+' isnt indicator 504 if '+' isnt indicator
508 # Remove any extra space or new line as we are adding them after 505 # Remove any extra space or new line as we are adding them after
509 text = Utils.rtrim(text) 506 text = Utils.rtrim(text)
510   507  
511 # Deal with trailing newlines as indicated 508 # Deal with trailing newlines as indicated
512 if '' is indicator 509 if '' is indicator
513 text = @PATTERN_TRAILING_LINES.replace text, "\n" 510 text = @PATTERN_TRAILING_LINES.replace text, "\n"
514 else if '-' is indicator 511 else if '-' is indicator
515 text = @PATTERN_TRAILING_LINES.replace text, '' 512 text = @PATTERN_TRAILING_LINES.replace text, ''
516   513  
517 return text 514 return text
518   515  
519   516  
520 # Returns true if the next line is indented. 517 # Returns true if the next line is indented.
521 # 518 #
522 # @return [Boolean] Returns true if the next line is indented, false otherwise 519 # @return [Boolean] Returns true if the next line is indented, false otherwise
523 # 520 #
524 isNextLineIndented: (ignoreComments = true) -> 521 isNextLineIndented: (ignoreComments = true) ->
525 currentIndentation = @getCurrentLineIndentation() 522 currentIndentation = @getCurrentLineIndentation()
526 EOF = not @moveToNextLine() 523 EOF = not @moveToNextLine()
527   524  
528 if ignoreComments 525 if ignoreComments
529 while not(EOF) and @isCurrentLineEmpty() 526 while not(EOF) and @isCurrentLineEmpty()
530 EOF = not @moveToNextLine() 527 EOF = not @moveToNextLine()
531 else 528 else
532 while not(EOF) and @isCurrentLineBlank() 529 while not(EOF) and @isCurrentLineBlank()
533 EOF = not @moveToNextLine() 530 EOF = not @moveToNextLine()
534   531  
535 if EOF 532 if EOF
536 return false 533 return false
537   534  
538 ret = false 535 ret = false
539 if @getCurrentLineIndentation() > currentIndentation 536 if @getCurrentLineIndentation() > currentIndentation
540 ret = true 537 ret = true
541   538  
542 @moveToPreviousLine() 539 @moveToPreviousLine()
543   540  
544 return ret 541 return ret
545   542  
546   543  
547 # Returns true if the current line is blank or if it is a comment line. 544 # Returns true if the current line is blank or if it is a comment line.
548 # 545 #
549 # @return [Boolean] Returns true if the current line is empty or if it is a comment line, false otherwise 546 # @return [Boolean] Returns true if the current line is empty or if it is a comment line, false otherwise
550 # 547 #
551 isCurrentLineEmpty: -> 548 isCurrentLineEmpty: ->
552 trimmedLine = Utils.trim(@currentLine, ' ') 549 trimmedLine = Utils.trim(@currentLine, ' ')
553 return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#' 550 return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#'
554   551  
555   552  
556 # Returns true if the current line is blank. 553 # Returns true if the current line is blank.
557 # 554 #
558 # @return [Boolean] Returns true if the current line is blank, false otherwise 555 # @return [Boolean] Returns true if the current line is blank, false otherwise
559 # 556 #
560 isCurrentLineBlank: -> 557 isCurrentLineBlank: ->
561 return '' is Utils.trim(@currentLine, ' ') 558 return '' is Utils.trim(@currentLine, ' ')
562   559  
563   560  
564 # Returns true if the current line is a comment line. 561 # Returns true if the current line is a comment line.
565 # 562 #
566 # @return [Boolean] Returns true if the current line is a comment line, false otherwise 563 # @return [Boolean] Returns true if the current line is a comment line, false otherwise
567 # 564 #
568 isCurrentLineComment: -> 565 isCurrentLineComment: ->
569 # Checking explicitly the first char of the trim is faster than loops or strpos 566 # Checking explicitly the first char of the trim is faster than loops or strpos
570 ltrimmedLine = Utils.ltrim(@currentLine, ' ') 567 ltrimmedLine = Utils.ltrim(@currentLine, ' ')
571   568  
572 return ltrimmedLine.charAt(0) is '#' 569 return ltrimmedLine.charAt(0) is '#'
573   570  
574   571  
575 # Cleanups a YAML string to be parsed. 572 # Cleanups a YAML string to be parsed.
576 # 573 #
577 # @param [String] value The input YAML string 574 # @param [String] value The input YAML string
578 # 575 #
579 # @return [String] A cleaned up YAML string 576 # @return [String] A cleaned up YAML string
580 # 577 #
581 cleanup: (value) -> 578 cleanup: (value) ->
582 if value.indexOf("\r") isnt -1 579 if value.indexOf("\r") isnt -1
583 value = value.split("\r\n").join("\n").split("\r").join("\n") 580 value = value.split("\r\n").join("\n").split("\r").join("\n")
584   581  
585 # Strip YAML header 582 # Strip YAML header
586 count = 0 583 count = 0
587 [value, count] = @PATTERN_YAML_HEADER.replaceAll value, '' 584 [value, count] = @PATTERN_YAML_HEADER.replaceAll value, ''
588 @offset += count 585 @offset += count
589   586  
590 # Remove leading comments 587 # Remove leading comments
591 [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1 588 [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1
592 if count is 1 589 if count is 1
593 # Items have been removed, update the offset 590 # Items have been removed, update the offset
594 @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n") 591 @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
595 value = trimmedValue 592 value = trimmedValue
596   593  
597 # Remove start of the document marker (---) 594 # Remove start of the document marker (---)
598 [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1 595 [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1
599 if count is 1 596 if count is 1
600 # Items have been removed, update the offset 597 # Items have been removed, update the offset
601 @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n") 598 @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
602 value = trimmedValue 599 value = trimmedValue
603   600  
604 # Remove end of the document marker (...) 601 # Remove end of the document marker (...)
605 value = @PATTERN_DOCUMENT_MARKER_END.replace value, '' 602 value = @PATTERN_DOCUMENT_MARKER_END.replace value, ''
606   603  
607 # Ensure the block is not indented 604 # Ensure the block is not indented
608 lines = value.split("\n") 605 lines = value.split("\n")
609 smallestIndent = -1 606 smallestIndent = -1
610 for line in lines 607 for line in lines
611 continue if Utils.trim(line, ' ').length == 0 608 continue if Utils.trim(line, ' ').length == 0
612 indent = line.length - Utils.ltrim(line).length 609 indent = line.length - Utils.ltrim(line).length
613 if smallestIndent is -1 or indent < smallestIndent 610 if smallestIndent is -1 or indent < smallestIndent
614 smallestIndent = indent 611 smallestIndent = indent
615 if smallestIndent > 0 612 if smallestIndent > 0
616 for line, i in lines 613 for line, i in lines
617 lines[i] = line[smallestIndent..] 614 lines[i] = line[smallestIndent..]
618 value = lines.join("\n") 615 value = lines.join("\n")
619   616  
620 return value 617 return value
621   618  
622   619  
623 # Returns true if the next line starts unindented collection 620 # Returns true if the next line starts unindented collection
624 # 621 #
625 # @return [Boolean] Returns true if the next line starts unindented collection, false otherwise 622 # @return [Boolean] Returns true if the next line starts unindented collection, false otherwise
626 # 623 #
627 isNextLineUnIndentedCollection: (currentIndentation = null) -> 624 isNextLineUnIndentedCollection: (currentIndentation = null) ->
628 currentIndentation ?= @getCurrentLineIndentation() 625 currentIndentation ?= @getCurrentLineIndentation()
629 notEOF = @moveToNextLine() 626 notEOF = @moveToNextLine()
630   627  
631 while notEOF and @isCurrentLineEmpty() 628 while notEOF and @isCurrentLineEmpty()
632 notEOF = @moveToNextLine() 629 notEOF = @moveToNextLine()
633   630  
634 if false is notEOF 631 if false is notEOF
635 return false 632 return false
636   633  
637 ret = false 634 ret = false
638 if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine) 635 if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine)
639 ret = true 636 ret = true
640   637  
641 @moveToPreviousLine() 638 @moveToPreviousLine()
642   639  
643 return ret 640 return ret
644   641  
645   642  
646 # Returns true if the string is un-indented collection item 643 # Returns true if the string is un-indented collection item
647 # 644 #
648 # @return [Boolean] Returns true if the string is un-indented collection item, false otherwise 645 # @return [Boolean] Returns true if the string is un-indented collection item, false otherwise
649 # 646 #
650 isStringUnIndentedCollectionItem: -> 647 isStringUnIndentedCollectionItem: ->
651 return @currentLine is '-' or @currentLine[0...2] is '- ' 648 return @currentLine is '-' or @currentLine[0...2] is '- '
652   649  
653   650  
654 module.exports = Parser 651 module.exports = Parser
655   652