feat(News): refactor news service and add sentiment tracking
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.AriaKeyError = void 0;
|
||||
exports.parseAriaKey = parseAriaKey;
|
||||
exports.parseYamlTemplate = parseYamlTemplate;
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// https://www.w3.org/TR/wai-aria-1.2/#role_definitions
|
||||
|
||||
function parseYamlTemplate(fragment) {
|
||||
const result = {
|
||||
kind: 'role',
|
||||
role: 'fragment'
|
||||
};
|
||||
populateNode(result, fragment);
|
||||
if (result.children && result.children.length === 1) return result.children[0];
|
||||
return result;
|
||||
}
|
||||
function populateNode(node, container) {
|
||||
for (const object of container) {
|
||||
if (typeof object === 'string') {
|
||||
const childNode = KeyParser.parse(object);
|
||||
node.children = node.children || [];
|
||||
node.children.push(childNode);
|
||||
continue;
|
||||
}
|
||||
for (const key of Object.keys(object)) {
|
||||
node.children = node.children || [];
|
||||
const value = object[key];
|
||||
if (key === 'text') {
|
||||
node.children.push({
|
||||
kind: 'text',
|
||||
text: valueOrRegex(value)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const childNode = KeyParser.parse(key);
|
||||
if (childNode.kind === 'text') {
|
||||
node.children.push({
|
||||
kind: 'text',
|
||||
text: valueOrRegex(value)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
node.children.push({
|
||||
...childNode,
|
||||
children: [{
|
||||
kind: 'text',
|
||||
text: valueOrRegex(value)
|
||||
}]
|
||||
});
|
||||
continue;
|
||||
}
|
||||
node.children.push(childNode);
|
||||
populateNode(childNode, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
function normalizeWhitespace(text) {
|
||||
return text.replace(/[\r\n\s\t]+/g, ' ').trim();
|
||||
}
|
||||
function valueOrRegex(value) {
|
||||
return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : normalizeWhitespace(value);
|
||||
}
|
||||
class KeyParser {
|
||||
static parse(input) {
|
||||
return new KeyParser(input)._parse();
|
||||
}
|
||||
constructor(input) {
|
||||
this._input = void 0;
|
||||
this._pos = void 0;
|
||||
this._length = void 0;
|
||||
this._input = input;
|
||||
this._pos = 0;
|
||||
this._length = input.length;
|
||||
}
|
||||
_peek() {
|
||||
return this._input[this._pos] || '';
|
||||
}
|
||||
_next() {
|
||||
if (this._pos < this._length) return this._input[this._pos++];
|
||||
return null;
|
||||
}
|
||||
_eof() {
|
||||
return this._pos >= this._length;
|
||||
}
|
||||
_isWhitespace() {
|
||||
return !this._eof() && /\s/.test(this._peek());
|
||||
}
|
||||
_skipWhitespace() {
|
||||
while (this._isWhitespace()) this._pos++;
|
||||
}
|
||||
_readIdentifier(type) {
|
||||
if (this._eof()) this._throwError(`Unexpected end of input when expecting ${type}`);
|
||||
const start = this._pos;
|
||||
while (!this._eof() && /[a-zA-Z]/.test(this._peek())) this._pos++;
|
||||
return this._input.slice(start, this._pos);
|
||||
}
|
||||
_readString() {
|
||||
let result = '';
|
||||
let escaped = false;
|
||||
while (!this._eof()) {
|
||||
const ch = this._next();
|
||||
if (escaped) {
|
||||
result += ch;
|
||||
escaped = false;
|
||||
} else if (ch === '\\') {
|
||||
escaped = true;
|
||||
} else if (ch === '"') {
|
||||
return result;
|
||||
} else {
|
||||
result += ch;
|
||||
}
|
||||
}
|
||||
this._throwError('Unterminated string');
|
||||
}
|
||||
_throwError(message, pos) {
|
||||
throw new AriaKeyError(message, this._input, pos || this._pos);
|
||||
}
|
||||
_readRegex() {
|
||||
let result = '';
|
||||
let escaped = false;
|
||||
let insideClass = false;
|
||||
while (!this._eof()) {
|
||||
const ch = this._next();
|
||||
if (escaped) {
|
||||
result += ch;
|
||||
escaped = false;
|
||||
} else if (ch === '\\') {
|
||||
escaped = true;
|
||||
result += ch;
|
||||
} else if (ch === '/' && !insideClass) {
|
||||
return result;
|
||||
} else if (ch === '[') {
|
||||
insideClass = true;
|
||||
result += ch;
|
||||
} else if (ch === ']' && insideClass) {
|
||||
result += ch;
|
||||
insideClass = false;
|
||||
} else {
|
||||
result += ch;
|
||||
}
|
||||
}
|
||||
this._throwError('Unterminated regex');
|
||||
}
|
||||
_readStringOrRegex() {
|
||||
const ch = this._peek();
|
||||
if (ch === '"') {
|
||||
this._next();
|
||||
return this._readString();
|
||||
}
|
||||
if (ch === '/') {
|
||||
this._next();
|
||||
return new RegExp(this._readRegex());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
_readAttributes(result) {
|
||||
let errorPos = this._pos;
|
||||
while (true) {
|
||||
this._skipWhitespace();
|
||||
if (this._peek() === '[') {
|
||||
this._next();
|
||||
this._skipWhitespace();
|
||||
errorPos = this._pos;
|
||||
const flagName = this._readIdentifier('attribute');
|
||||
this._skipWhitespace();
|
||||
let flagValue = '';
|
||||
if (this._peek() === '=') {
|
||||
this._next();
|
||||
this._skipWhitespace();
|
||||
errorPos = this._pos;
|
||||
while (this._peek() !== ']' && !this._isWhitespace() && !this._eof()) flagValue += this._next();
|
||||
}
|
||||
this._skipWhitespace();
|
||||
if (this._peek() !== ']') this._throwError('Expected ]');
|
||||
this._next(); // Consume ']'
|
||||
this._applyAttribute(result, flagName, flagValue || 'true', errorPos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_parse() {
|
||||
this._skipWhitespace();
|
||||
const role = this._readIdentifier('role');
|
||||
this._skipWhitespace();
|
||||
const name = this._readStringOrRegex() || '';
|
||||
const result = {
|
||||
kind: 'role',
|
||||
role,
|
||||
name
|
||||
};
|
||||
this._readAttributes(result);
|
||||
this._skipWhitespace();
|
||||
if (!this._eof()) this._throwError('Unexpected input');
|
||||
return result;
|
||||
}
|
||||
_applyAttribute(node, key, value, errorPos) {
|
||||
if (key === 'checked') {
|
||||
this._assert(value === 'true' || value === 'false' || value === 'mixed', 'Value of "checked\" attribute must be a boolean or "mixed"', errorPos);
|
||||
node.checked = value === 'true' ? true : value === 'false' ? false : 'mixed';
|
||||
return;
|
||||
}
|
||||
if (key === 'disabled') {
|
||||
this._assert(value === 'true' || value === 'false', 'Value of "disabled" attribute must be a boolean', errorPos);
|
||||
node.disabled = value === 'true';
|
||||
return;
|
||||
}
|
||||
if (key === 'expanded') {
|
||||
this._assert(value === 'true' || value === 'false', 'Value of "expanded" attribute must be a boolean', errorPos);
|
||||
node.expanded = value === 'true';
|
||||
return;
|
||||
}
|
||||
if (key === 'level') {
|
||||
this._assert(!isNaN(Number(value)), 'Value of "level" attribute must be a number', errorPos);
|
||||
node.level = Number(value);
|
||||
return;
|
||||
}
|
||||
if (key === 'pressed') {
|
||||
this._assert(value === 'true' || value === 'false' || value === 'mixed', 'Value of "pressed" attribute must be a boolean or "mixed"', errorPos);
|
||||
node.pressed = value === 'true' ? true : value === 'false' ? false : 'mixed';
|
||||
return;
|
||||
}
|
||||
if (key === 'selected') {
|
||||
this._assert(value === 'true' || value === 'false', 'Value of "selected" attribute must be a boolean', errorPos);
|
||||
node.selected = value === 'true';
|
||||
return;
|
||||
}
|
||||
this._assert(false, `Unsupported attribute [${key}]`, errorPos);
|
||||
}
|
||||
_assert(value, message, valuePos) {
|
||||
if (!value) this._throwError(message || 'Assertion error', valuePos);
|
||||
}
|
||||
}
|
||||
function parseAriaKey(key) {
|
||||
return KeyParser.parse(key);
|
||||
}
|
||||
class AriaKeyError extends Error {
|
||||
constructor(message, input, pos) {
|
||||
super(message + ':\n\n' + input + '\n' + ' '.repeat(pos) + '^\n');
|
||||
this.shortMessage = void 0;
|
||||
this.pos = void 0;
|
||||
this.shortMessage = message;
|
||||
this.pos = pos;
|
||||
this.stack = undefined;
|
||||
}
|
||||
}
|
||||
exports.AriaKeyError = AriaKeyError;
|
||||
Reference in New Issue
Block a user