1 |
wakaba |
1.1 |
<!DOCTYPE HTML> |
2 |
wakaba |
1.2 |
<title><meter> processing and authoring requirements</title> |
3 |
wakaba |
1.1 |
|
4 |
wakaba |
1.3 |
<h1><meter></h1> |
5 |
|
|
|
6 |
wakaba |
1.1 |
<script> |
7 |
|
|
|
8 |
|
|
// Web Applications 1.0 Revision 4116, 12 October 2009 |
9 |
|
|
|
10 |
|
|
var whiteSpace = /\s/; // XXX White_Space != \s |
11 |
|
|
var denominatorPunctuationCharacter = /[\u0025\u066A\uFE6A\uFF05\u2030\u2031]/; |
12 |
|
|
|
13 |
|
|
function findingOneOrTwoNumbersOfARatioInAString (string) { |
14 |
|
|
// 1. If the string is empty, then return nothing and abort these steps. |
15 |
|
|
if (string == '') { |
16 |
|
|
return {isNothing: true, step: 1}; |
17 |
|
|
} |
18 |
|
|
|
19 |
|
|
// 2. Find a number in the string according to the algorithm below, starting at the start of the string. |
20 |
|
|
var position = 0; |
21 |
|
|
var s2 = findANumber (string, position); |
22 |
|
|
|
23 |
|
|
// 3. If the sub-algorithm in step 2 returned nothing or returned an error condition, return nothing and abort these steps. |
24 |
|
|
if (s2.isNothing || s2.isError) { |
25 |
|
|
return {isNothing: true, step: 3}; |
26 |
|
|
} |
27 |
|
|
|
28 |
|
|
// 4. Set number1 to the number returned by the sub-algorithm in step 2. |
29 |
|
|
var number1 = s2.number; |
30 |
|
|
|
31 |
|
|
// 5. Starting with the character immediately after the last one examined by the sub-algorithm in step 2, skip all White_Space characters in the string (this might match zero characters). |
32 |
|
|
position = s2.position; |
33 |
|
|
while (position < string.length && string.charAt (position).match (whiteSpace)) { |
34 |
|
|
position++; |
35 |
|
|
} |
36 |
|
|
|
37 |
|
|
// 6. If there are still further characters in the string, and the next character in the string is a valid denominator punctuation character, set denominator to that character. |
38 |
|
|
var denominator = null; |
39 |
|
|
if (position < string.length && string.charAt (position).match (denominatorPunctuationCharacter)) { |
40 |
|
|
denominator = string.charAt (position); |
41 |
|
|
} |
42 |
|
|
|
43 |
|
|
// 7. If the string contains any other characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), but denominator was given a value in the step 6, return nothing and abort these steps. |
44 |
|
|
if (string.substring (position).match (/[0-9]/) && denominator != null) { |
45 |
|
|
return {isNothing: true, step: 7}; |
46 |
|
|
} |
47 |
|
|
|
48 |
|
|
// 8. Otherwise, if denominator was given a value in step 6, return number1 and denominator and abort these steps. |
49 |
|
|
if (denominator != null) { |
50 |
|
|
return {number1: number1, denominator: denominator, step: 8}; |
51 |
|
|
} |
52 |
|
|
|
53 |
|
|
// 9. Find a number in the string again, starting immediately after the last character that was examined by the sub-algorithm in step 2. |
54 |
|
|
var s9 = findANumber (string, position); |
55 |
|
|
|
56 |
|
|
// 10. If the sub-algorithm in step 9 returned nothing or an error condition, return number1 and abort these steps. |
57 |
|
|
if (s9.isNothing || s9.isError) { |
58 |
|
|
return {number1: number1, step: 10}; |
59 |
|
|
} |
60 |
|
|
|
61 |
|
|
// 11. Set number2 to the number returned by the sub-algorithm in step 9. |
62 |
|
|
var number2 = s9.number; |
63 |
|
|
|
64 |
|
|
// 12. Starting with the character immediately after the last one examined by the sub-algorithm in step 9, skip all White_Space characters in the string (this might match zero characters). |
65 |
|
|
position = s9.position; |
66 |
|
|
while (position < string.length && string.charAt (position).match (whiteSpace)) { |
67 |
|
|
position++; |
68 |
|
|
} |
69 |
|
|
|
70 |
|
|
// 13. If there are still further characters in the string, and the next character in the string is a valid denominator punctuation character, return nothing and abort these steps. |
71 |
|
|
if (position < string.length && string.charAt (position).match (denominatorPunctuationCharacter)) { |
72 |
|
|
return {isNothing: true, step: 13}; |
73 |
|
|
} |
74 |
|
|
|
75 |
|
|
// 14. If the string contains any other characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), return nothing and abort these steps. |
76 |
|
|
if (string.substring (position).match (/[0-9]/)) { |
77 |
|
|
return {isNothing: true, step: 14}; |
78 |
|
|
} |
79 |
|
|
|
80 |
|
|
// 15. Otherwise, return number1 and number2. |
81 |
|
|
return {number1: number1, number2: number2, step: 15}; |
82 |
|
|
} // findingOneOrTwoNumbersOfARatioInAString |
83 |
|
|
|
84 |
|
|
function findANumber (givenString, position) { |
85 |
|
|
// 1. Starting at the given starting position, ignore all characters in the given string until the first character that is either a U+002E FULL STOP or one of the ten characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9). |
86 |
|
|
while (position < givenString.length && !givenString.charAt (position).match (/[0-9.]/)) { |
87 |
|
|
position++; |
88 |
|
|
} |
89 |
|
|
|
90 |
|
|
// 2. If there are no such characters, return nothing and abort these steps. |
91 |
|
|
if (position < givenString.length && givenString.charAt (position).match (/[0-9.]/)) { |
92 |
|
|
// |
93 |
|
|
} else { |
94 |
|
|
return {isNothing: true, position: position}; |
95 |
|
|
} |
96 |
|
|
|
97 |
|
|
// 3. Starting with the character matched in step 1, collect all the consecutive characters that are either a U+002E FULL STOP or one of the ten characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), and assign this string of one or more characters to string. |
98 |
|
|
var string = ''; |
99 |
|
|
while (position < givenString.length && givenString.charAt (position).match (/[0-9.]/)) { |
100 |
|
|
string += givenString.charAt (position); |
101 |
|
|
position++; |
102 |
|
|
} |
103 |
|
|
|
104 |
|
|
// 4. If string consists of just a single U+002E FULL STOP character or if it contains more than one U+002E FULL STOP character then return an error condition and abort these steps. |
105 |
|
|
if (string == '.' || string.match (/\..*\./)) { |
106 |
|
|
return {isError: true, position: position}; |
107 |
|
|
} |
108 |
|
|
|
109 |
|
|
// 5. Parse string according to the rules for parsing floating point number values, to obtain number. This step cannot fail (string is guaranteed to be a valid floating point number). |
110 |
|
|
var s5 = parseFloatingPointNumberValue (string); |
111 |
|
|
var number = s5.value; |
112 |
|
|
|
113 |
|
|
// 6. Return number. |
114 |
|
|
return {number: number, position: position}; |
115 |
|
|
} // findANumber |
116 |
|
|
|
117 |
|
|
function parseFloatingPointNumberValue (input) { |
118 |
|
|
// 1. Let input be the string being parsed. |
119 |
|
|
// |
120 |
|
|
|
121 |
|
|
// 2. Let position be a pointer into input, initially pointing at the start of the string. |
122 |
|
|
var position = 0; |
123 |
|
|
|
124 |
|
|
// 3. Let value have the value 1. |
125 |
|
|
var value = 1; |
126 |
|
|
|
127 |
|
|
// 4. Let divisor have the value 1. |
128 |
|
|
var divisor = 1; |
129 |
|
|
|
130 |
|
|
// 5. Let exponent have the value 1. |
131 |
|
|
var exponent = 1; |
132 |
|
|
|
133 |
|
|
// 6. Skip whitespace. |
134 |
|
|
var s6 = skipWhitespace (input, position); |
135 |
|
|
position = s6.position; |
136 |
|
|
|
137 |
|
|
// 7. If position is past the end of input, return an error. |
138 |
|
|
if (position >= input.length) { |
139 |
|
|
return {isError: true}; |
140 |
|
|
} |
141 |
|
|
|
142 |
|
|
// 8. If the character indicated by position is a U+002D HYPHEN-MINUS character (-): |
143 |
|
|
if (input.charAt (position) == '-') { |
144 |
|
|
// 1. Change value and divisor to −1. |
145 |
|
|
value = -1; |
146 |
|
|
divisor = -1; |
147 |
|
|
|
148 |
|
|
// 2. Advance position to the next character. |
149 |
|
|
position++; |
150 |
|
|
|
151 |
|
|
// 3. If position is past the end of input, return an error. |
152 |
|
|
if (position >= input.length) { |
153 |
|
|
return {isError: true}; |
154 |
|
|
} |
155 |
|
|
} |
156 |
|
|
|
157 |
|
|
// 9. If the character indicated by position is not one of U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then return an error. |
158 |
|
|
if (!input.charAt (position).match (/[0-9]/)) { |
159 |
|
|
return {isError: true}; |
160 |
|
|
} |
161 |
|
|
|
162 |
|
|
// 10. Collect a sequence of characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), and interpret the resulting sequence as a base-ten integer. Multiply value by that integer. |
163 |
|
|
var s10 = collectASequenceOfCharacters (/[0-9]/, input, position); |
164 |
|
|
position = s10.position; |
165 |
|
|
value *= parseInt (s10.result); |
166 |
|
|
|
167 |
|
|
// 11. If position is past the end of input, return value. |
168 |
|
|
if (position >= input.length) { |
169 |
|
|
return {value: value}; |
170 |
|
|
} |
171 |
|
|
|
172 |
|
|
// 12. If the character indicated by position is a U+002E FULL STOP (.), run these substeps: |
173 |
|
|
if (input.charAt (position) == '.') { |
174 |
|
|
// 1. Advance position to the next character. |
175 |
|
|
position++; |
176 |
|
|
|
177 |
|
|
// 2. If position is past the end of input, or if the character indicated by position is not one of U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then return value. |
178 |
|
|
if (position >= input.length || |
179 |
|
|
!input.charAt (position).match (/[0-9]/)) { |
180 |
|
|
return {value: value}; |
181 |
|
|
} |
182 |
|
|
|
183 |
|
|
// 3. Fraction loop: Multiply divisor by ten. |
184 |
|
|
var fractionLoop = true; |
185 |
|
|
while (fractionLoop) { |
186 |
|
|
divisor *= 10; |
187 |
|
|
|
188 |
|
|
// 4. Add the value of the current character interpreted as a base-ten digit (0..9) divided by divisor, to value. |
189 |
|
|
value += parseInt (input.charAt (position)) / divisor; |
190 |
|
|
|
191 |
|
|
// 5. Advance position to the next character. |
192 |
|
|
position++; |
193 |
|
|
|
194 |
|
|
// 6. If position is past the end of input, then return value. |
195 |
|
|
if (position >= input.length) { |
196 |
|
|
return {value: value}; |
197 |
|
|
} |
198 |
|
|
|
199 |
|
|
// 7. If the character indicated by position is one of U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), return to the step labeled fraction loop in these substeps. |
200 |
|
|
if (input.charAt (position).match (/[0-9]/)) { |
201 |
|
|
fractionLoop = true; |
202 |
|
|
} else { |
203 |
|
|
fractionLoop = false; |
204 |
|
|
} |
205 |
|
|
} |
206 |
|
|
} |
207 |
|
|
|
208 |
|
|
// 13. If the character indicated by position is a U+0065 LATIN SMALL LETTER E character (e) or a U+0045 LATIN CAPITAL LETTER E character (E), run these substeps: |
209 |
|
|
if (input.charAt (position).match (/[eE]/)) { |
210 |
|
|
// 1. Advance position to the next character. |
211 |
|
|
position++; |
212 |
|
|
|
213 |
|
|
// 2. If position is past the end of input, then return value. |
214 |
|
|
if (position >= input.length) { |
215 |
|
|
return {value: value}; |
216 |
|
|
} |
217 |
|
|
|
218 |
|
|
// 3. If the character indicated by position is a U+002D HYPHEN-MINUS character (-): |
219 |
|
|
if (input.charAt (position) == '-') { |
220 |
|
|
// 1. Change exponent to −1. |
221 |
|
|
exponent = -1; |
222 |
|
|
|
223 |
|
|
// 2. Advance position to the next character. |
224 |
|
|
position++; |
225 |
|
|
|
226 |
|
|
// 3. If position is past the end of input, then return value. |
227 |
|
|
if (position >= input.length) { |
228 |
|
|
return {value: value}; |
229 |
|
|
} |
230 |
|
|
|
231 |
|
|
// Otherwise, if the character indicated by position is a U+002B PLUS SIGN character (+): |
232 |
|
|
} else if (input.charAt (position) == '+') { |
233 |
|
|
// 1. Advance position to the next character. |
234 |
|
|
position++; |
235 |
|
|
|
236 |
|
|
// 2. If position is past the end of input, then return value. |
237 |
|
|
if (position >= input.length) { |
238 |
|
|
return {value: value}; |
239 |
|
|
} |
240 |
|
|
} |
241 |
|
|
|
242 |
|
|
// 4. If the character indicated by position is not one of U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then return value. |
243 |
|
|
if (!input.charAt (position).match (/[0-9]/)) { |
244 |
|
|
return {value: value}; |
245 |
|
|
} |
246 |
|
|
|
247 |
|
|
// 5. Collect a sequence of characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), and interpret the resulting sequence as a base-ten integer. Multiply exponent by that integer. |
248 |
|
|
var s13_5 = collectASequenceOfCharacters (/[0-9]/, input, position); |
249 |
|
|
position = s13_5.position; |
250 |
|
|
exponent *= parseInt (s13_5.result); |
251 |
|
|
|
252 |
|
|
// 6. Multiply value by ten raised to the exponentth power. |
253 |
|
|
value *= Math.pow (10, exponent); |
254 |
|
|
} |
255 |
|
|
|
256 |
|
|
// 14. Return value. |
257 |
|
|
return {value: value}; |
258 |
|
|
} // parseFloatingPointNumberValue |
259 |
|
|
|
260 |
|
|
function skipWhitespace (input, position) { |
261 |
|
|
return collectASequenceOfCharacters (/[\u0009\u000A\u000C\u000D\u0020]/, input, position); |
262 |
|
|
} // skipWhitespace |
263 |
|
|
|
264 |
|
|
function collectASequenceOfCharacters (characters, input, position) { |
265 |
|
|
// 1. Let input and position be the same variables as those of the same name in the algorithm that invoked these steps. |
266 |
|
|
// |
267 |
|
|
|
268 |
|
|
// 2. Let result be the empty string. |
269 |
|
|
var result = ''; |
270 |
|
|
|
271 |
|
|
// 3. While position doesn't point past the end of input and the character at position is one of the characters, append that character to the end of result and advance position to the next character in input. |
272 |
|
|
while (position < input.length && input.charAt (position).match (characters)) { |
273 |
|
|
result += input.charAt (position); |
274 |
|
|
position++; |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
// 4. Return result. |
278 |
|
|
return {result: result, position: position}; |
279 |
|
|
} // collectASequenceOfCharacters |
280 |
|
|
|
281 |
wakaba |
1.2 |
function tokenizeMeterContent (string) { |
282 |
wakaba |
1.1 |
var position = 0; |
283 |
|
|
var tokens = []; |
284 |
|
|
var lastToken = {type: 'initial', value: ''}; |
285 |
|
|
while (position < string.length) { |
286 |
|
|
var char = string.charAt (position); |
287 |
|
|
var tokenType; |
288 |
|
|
if (char.match (/[0-9]/)) { |
289 |
|
|
tokenType = 'number'; |
290 |
|
|
} else if (char.match (/\./)) { |
291 |
|
|
if (lastToken.type == 'number' && !lastToken.value.match (/\./)) { |
292 |
|
|
tokenType = 'number'; |
293 |
|
|
} else { |
294 |
|
|
tokenType = 'dot'; |
295 |
|
|
} |
296 |
|
|
} else if (char.match (whiteSpace)) { |
297 |
|
|
if (lastToken.type == 'number' || lastToken.type == 'whitespace') { |
298 |
|
|
tokenType = 'whitespace'; |
299 |
|
|
} else { |
300 |
|
|
tokenType = 'string'; |
301 |
|
|
} |
302 |
|
|
} else if (char.match (denominatorPunctuationCharacter) && (lastToken.type == 'whitespace' || lastToken.type == 'number')) { |
303 |
|
|
tokenType = 'denominator'; |
304 |
|
|
} else { |
305 |
|
|
tokenType = 'string'; |
306 |
|
|
} |
307 |
|
|
if (tokenType == lastToken.type) { |
308 |
|
|
lastToken.value += char; |
309 |
|
|
} else { |
310 |
|
|
lastToken = {type: tokenType, value: char}; |
311 |
|
|
tokens.push (lastToken); |
312 |
|
|
} |
313 |
|
|
position++; |
314 |
|
|
} |
315 |
|
|
|
316 |
|
|
return tokens; |
317 |
wakaba |
1.2 |
} // tokenizeMeterContent |
318 |
wakaba |
1.1 |
|
319 |
|
|
function extractNumbersFromTokens (tokens) { |
320 |
|
|
var number1 = undefined; |
321 |
|
|
var number2 = undefined; |
322 |
|
|
var denominator = undefined; |
323 |
|
|
for (var i = 0; i < tokens.length; i++) { |
324 |
|
|
var token = tokens[i]; |
325 |
|
|
if (token.type == 'number') { |
326 |
|
|
if (number1 == undefined) { |
327 |
|
|
number1 = parseFloat (token.value); |
328 |
|
|
} else if (denominator != undefined) { |
329 |
|
|
return {isNothing: true}; |
330 |
|
|
} else if (number2 == undefined) { |
331 |
|
|
number2 = parseFloat (token.value); |
332 |
|
|
} else { |
333 |
|
|
return {isNothing: true}; |
334 |
|
|
} |
335 |
|
|
} else if (token.type == 'denominator') { |
336 |
|
|
if (number2 != undefined) { |
337 |
|
|
return {isNothing: true}; |
338 |
|
|
} else { |
339 |
|
|
denominator = token.value; |
340 |
|
|
} |
341 |
|
|
} else if (token.type == 'dot') { |
342 |
|
|
return {isNothing: true}; |
343 |
|
|
} |
344 |
|
|
} |
345 |
|
|
|
346 |
|
|
if (number1 == undefined) { |
347 |
|
|
return {isNothing: true}; |
348 |
|
|
} else { |
349 |
|
|
return {number1: number1, number2: number2, denominator: denominator}; |
350 |
|
|
} |
351 |
|
|
} // extractNumbersFromTokens |
352 |
|
|
|
353 |
|
|
function objectToString (object) { |
354 |
|
|
return self.JSON ? JSON.stringify (object) : object.toSource ? object.toSource () : object.toString (); |
355 |
|
|
} // objectToString |
356 |
|
|
|
357 |
wakaba |
1.2 |
function processMeterContent (string) { |
358 |
wakaba |
1.1 |
var result = {}; |
359 |
|
|
|
360 |
|
|
var ua = findingOneOrTwoNumbersOfARatioInAString (string); |
361 |
|
|
for (var i in ua) { |
362 |
|
|
result['ua-' + i] = ua[i]; |
363 |
|
|
} |
364 |
|
|
|
365 |
wakaba |
1.2 |
var tokens = tokenizeMeterContent (string); |
366 |
wakaba |
1.1 |
result['author-tokens'] = objectToString (tokens); |
367 |
|
|
var author = extractNumbersFromTokens (tokens); |
368 |
|
|
for (var i in author) { |
369 |
|
|
result['author-' + i] = author[i]; |
370 |
|
|
} |
371 |
|
|
|
372 |
|
|
return result; |
373 |
wakaba |
1.2 |
} // processMeterContent |
374 |
wakaba |
1.1 |
|
375 |
|
|
function update (form) { |
376 |
|
|
var input = form.elements.input.value; |
377 |
|
|
|
378 |
wakaba |
1.2 |
var result = processMeterContent (input); |
379 |
wakaba |
1.1 |
|
380 |
|
|
var outputs = form.getElementsByTagName ('output'); |
381 |
|
|
for (var i = 0; i < outputs.length; i++) { |
382 |
|
|
var output = outputs[i]; |
383 |
|
|
var name = output.getAttribute ('name'); |
384 |
|
|
var value = result[name]; |
385 |
|
|
if (value === undefined) { |
386 |
|
|
output.textContent = '(undefined)'; |
387 |
|
|
} else if (value === null) { |
388 |
|
|
output.textContent = '(null)'; |
389 |
|
|
} else if (value === '') { |
390 |
|
|
output.textContent = '(empty)'; |
391 |
|
|
} else { |
392 |
|
|
output.textContent = value; |
393 |
|
|
} |
394 |
|
|
} |
395 |
|
|
} // update |
396 |
|
|
|
397 |
|
|
</script> |
398 |
|
|
|
399 |
|
|
<form> |
400 |
|
|
<p><input name=input onchange="update (this.form)"> |
401 |
|
|
|
402 |
|
|
<fieldset> |
403 |
|
|
<legend>From UA parsing rules</legend> |
404 |
|
|
|
405 |
|
|
<p><code>step</code>: <output name=ua-step></output> |
406 |
|
|
<p><code>isNothing</code>: <output name=ua-isNothing></output> |
407 |
|
|
<p><code>number1</code>: <output name=ua-number1></output> |
408 |
|
|
<p><code>number2</code>: <output name=ua-number2></output> |
409 |
|
|
<p><code>denominator</code>: <output name=ua-denominator></output> |
410 |
|
|
</fieldset> |
411 |
|
|
|
412 |
|
|
<fieldset> |
413 |
|
|
<legend>From author requirements</legend> |
414 |
|
|
|
415 |
|
|
<p><code>isNothing</code>: <output name=author-isNothing></output> |
416 |
|
|
<p><code>number1</code>: <output name=author-number1></output> |
417 |
|
|
<p><code>number2</code>: <output name=author-number2></output> |
418 |
|
|
<p><code>denominator</code>: <output name=author-denominator></output> |
419 |
|
|
<p>Tokens: <output name=author-tokens></output> |
420 |
|
|
</fieldset> |
421 |
|
|
</form> |
422 |
|
|
|
423 |
|
|
<script> |
424 |
|
|
if (location.href.match (/#/)) { |
425 |
|
|
var fragment = decodeURIComponent (location.href.split (/#/, 2)[1]); |
426 |
|
|
document.forms[0].elements.input.value = fragment; |
427 |
|
|
update (document.forms[0]); |
428 |
|
|
} |
429 |
|
|
</script> |