|
import { |
|
isDigit, |
|
isHexDigit, |
|
isUppercaseLetter, |
|
isName, |
|
isWhiteSpace, |
|
isValidEscape |
|
} from './char-code-definitions.js'; |
|
|
|
function getCharCode(source, offset) { |
|
return offset < source.length ? source.charCodeAt(offset) : 0; |
|
} |
|
|
|
export function getNewlineLength(source, offset, code) { |
|
if (code === 13 && getCharCode(source, offset + 1) === 10 ) { |
|
return 2; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
export function cmpChar(testStr, offset, referenceCode) { |
|
let code = testStr.charCodeAt(offset); |
|
|
|
|
|
if (isUppercaseLetter(code)) { |
|
code = code | 32; |
|
} |
|
|
|
return code === referenceCode; |
|
} |
|
|
|
export function cmpStr(testStr, start, end, referenceStr) { |
|
if (end - start !== referenceStr.length) { |
|
return false; |
|
} |
|
|
|
if (start < 0 || end > testStr.length) { |
|
return false; |
|
} |
|
|
|
for (let i = start; i < end; i++) { |
|
const referenceCode = referenceStr.charCodeAt(i - start); |
|
let testCode = testStr.charCodeAt(i); |
|
|
|
|
|
if (isUppercaseLetter(testCode)) { |
|
testCode = testCode | 32; |
|
} |
|
|
|
if (testCode !== referenceCode) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
export function findWhiteSpaceStart(source, offset) { |
|
for (; offset >= 0; offset--) { |
|
if (!isWhiteSpace(source.charCodeAt(offset))) { |
|
break; |
|
} |
|
} |
|
|
|
return offset + 1; |
|
} |
|
|
|
export function findWhiteSpaceEnd(source, offset) { |
|
for (; offset < source.length; offset++) { |
|
if (!isWhiteSpace(source.charCodeAt(offset))) { |
|
break; |
|
} |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
export function findDecimalNumberEnd(source, offset) { |
|
for (; offset < source.length; offset++) { |
|
if (!isDigit(source.charCodeAt(offset))) { |
|
break; |
|
} |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
|
|
export function consumeEscaped(source, offset) { |
|
|
|
|
|
offset += 2; |
|
|
|
|
|
if (isHexDigit(getCharCode(source, offset - 1))) { |
|
|
|
|
|
for (const maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) { |
|
if (!isHexDigit(getCharCode(source, offset))) { |
|
break; |
|
} |
|
} |
|
|
|
|
|
const code = getCharCode(source, offset); |
|
if (isWhiteSpace(code)) { |
|
offset += getNewlineLength(source, offset, code); |
|
} |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function consumeName(source, offset) { |
|
|
|
|
|
for (; offset < source.length; offset++) { |
|
const code = source.charCodeAt(offset); |
|
|
|
|
|
if (isName(code)) { |
|
|
|
continue; |
|
} |
|
|
|
|
|
if (isValidEscape(code, getCharCode(source, offset + 1))) { |
|
|
|
offset = consumeEscaped(source, offset) - 1; |
|
continue; |
|
} |
|
|
|
|
|
|
|
break; |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
|
|
export function consumeNumber(source, offset) { |
|
let code = source.charCodeAt(offset); |
|
|
|
|
|
|
|
if (code === 0x002B || code === 0x002D) { |
|
code = source.charCodeAt(offset += 1); |
|
} |
|
|
|
|
|
if (isDigit(code)) { |
|
offset = findDecimalNumberEnd(source, offset + 1); |
|
code = source.charCodeAt(offset); |
|
} |
|
|
|
|
|
if (code === 0x002E && isDigit(source.charCodeAt(offset + 1))) { |
|
|
|
|
|
offset += 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
offset = findDecimalNumberEnd(source, offset); |
|
} |
|
|
|
|
|
|
|
if (cmpChar(source, offset, 101 )) { |
|
let sign = 0; |
|
code = source.charCodeAt(offset + 1); |
|
|
|
|
|
if (code === 0x002D || code === 0x002B) { |
|
sign = 1; |
|
code = source.charCodeAt(offset + 2); |
|
} |
|
|
|
|
|
if (isDigit(code)) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
offset = findDecimalNumberEnd(source, offset + 1 + sign + 1); |
|
} |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
|
|
|
|
|
|
export function consumeBadUrlRemnants(source, offset) { |
|
|
|
for (; offset < source.length; offset++) { |
|
const code = source.charCodeAt(offset); |
|
|
|
|
|
|
|
if (code === 0x0029) { |
|
|
|
offset++; |
|
break; |
|
} |
|
|
|
if (isValidEscape(code, getCharCode(source, offset + 1))) { |
|
|
|
|
|
|
|
|
|
offset = consumeEscaped(source, offset); |
|
} |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
|
|
|
|
export function decodeEscaped(escaped) { |
|
|
|
if (escaped.length === 1 && !isHexDigit(escaped.charCodeAt(0))) { |
|
return escaped[0]; |
|
} |
|
|
|
|
|
let code = parseInt(escaped, 16); |
|
|
|
if ( |
|
(code === 0) || |
|
(code >= 0xD800 && code <= 0xDFFF) || |
|
(code > 0x10FFFF) |
|
) { |
|
|
|
code = 0xFFFD; |
|
} |
|
|
|
|
|
return String.fromCodePoint(code); |
|
} |
|
|