Skip to content

Commit

Permalink
Merge pull request #745 from Speech-Rule-Engine/refactor/speech_gener…
Browse files Browse the repository at this point in the history
…ator

Refactor/speech generator
  • Loading branch information
zorkow authored Jan 16, 2024
2 parents abed8c8 + 8d9e0d5 commit 0719c81
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 76 deletions.
2 changes: 2 additions & 0 deletions ts/enrich_mathml/enrich_attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export enum Attribute {
ROLE = 'data-semantic-role',
SPEECH = 'data-semantic-speech',
STRUCTURE = 'data-semantic-structure',
SUMMARY = 'data-semantic-summary',
TYPE = 'data-semantic-type'
}

Expand All @@ -77,6 +78,7 @@ export const EnrichAttributes: string[] = [
Attribute.ROLE,
Attribute.SPEECH,
Attribute.STRUCTURE,
Attribute.SUMMARY,
Attribute.TYPE
];

Expand Down
5 changes: 4 additions & 1 deletion ts/rule_engine/speech_rule_engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export class SpeechRuleEngine {
try {
result = this.evaluateNode_(node);
} catch (err) {
console.log(err);
console.error('Something went wrong computing speech.');
Debugger.getInstance().output(err);
}
Expand Down Expand Up @@ -327,7 +328,9 @@ export class SpeechRuleEngine {
'Apply Rule:',
rule.name,
rule.dynamicCstr.toString(),
(engine.mode !== EngineConst.Mode.HTTP ? node : node).toString()
engine.mode === EngineConst.Mode.HTTP ?
DomUtil.serializeXml(node) :
node.toString()
]);
Grammar.getInstance().processSingles();
const context = rule.context;
Expand Down
12 changes: 10 additions & 2 deletions ts/semantic_tree/semantic_skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import { SemanticTree } from './semantic_tree.js';

export type Sexp = number | Sexp[];

const Options = {
tree: false
}

/**
* @param skeleton The skeleton array.
*/
Expand Down Expand Up @@ -314,7 +318,10 @@ export class SemanticSkeleton {
SemanticSkeleton.tree_(mml, child, level + 1, i + 1, l) as any
);
}
SemanticSkeleton.addAria(mmlChild, level, posinset, setsize, level ? 'group' : 'tree');
SemanticSkeleton.addAria(
mmlChild, level, posinset, setsize,
!Options.tree ? 'treeitem' : (level ? 'group' : 'tree')
);
return skeleton;
}

Expand All @@ -323,7 +330,8 @@ export class SemanticSkeleton {
level: number,
posinset: number,
setsize: number,
role: string = level ? 'treeitem' : 'tree'
role: string = !Options.tree ? 'treeitem' :
(level ? 'treeitem' : 'tree')
) {
if (!Engine.getInstance().aria || !node) {
return;
Expand Down
101 changes: 94 additions & 7 deletions ts/speech_generator/abstract_speech_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { AxisMap } from '../rule_engine/dynamic_cstr.js';
import { RebuildStree } from '../walker/rebuild_stree.js';
import { SpeechGenerator } from './speech_generator.js';
import * as SpeechGeneratorUtil from './speech_generator_util.js';
import * as EngineConst from '../common/engine_const.js';
import { SemanticNode } from '../semantic_tree/semantic_node.js';

import { ClearspeakPreferences } from '../speech_rules/clearspeak_preferences.js';

export abstract class AbstractSpeechGenerator implements SpeechGenerator {
/**
Expand Down Expand Up @@ -62,27 +66,36 @@ export abstract class AbstractSpeechGenerator implements SpeechGenerator {
/**
* @override
*/
public setOptions(options: AxisMap) {
this.options_ = options || {};
this.modality = EnrichAttr.addPrefix(this.options_.modality || 'speech');
public computeRebuilt(xml: Element, force: boolean = false) {
if (!this.rebuilt_ || force) {
this.rebuilt_ = new RebuildStree(xml);
}
return this.rebuilt_;
}

/**
* @override
*/
public getOptions() {
return this.options_;
public setOptions(options: AxisMap) {
this.options_ = options || {};
this.modality = EnrichAttr.addPrefix(this.options_.modality || 'speech');
}

/**
* @override
*/
public start() {}
public setOption(key: string, value: string) {
const options = this.getOptions();
options[key] = value;
this.setOptions(options);
}

/**
* @override
*/
public end() {}
public getOptions() {
return this.options_;
}

/**
* @override
Expand All @@ -94,4 +107,78 @@ export abstract class AbstractSpeechGenerator implements SpeechGenerator {
EngineSetup(this.options_);
return SpeechGeneratorUtil.computeMarkup(this.getRebuilt().xml);
}

/**
* @override
*/
public nextRules() {
const options = this.getOptions();
// Rule cycling only makes sense for speech modality.
if (options.modality !== 'speech') {
return;
}
const prefs = ClearspeakPreferences.getLocalePreferences();
if (!prefs[options.locale]) {
return;
}
EngineConst.DOMAIN_TO_STYLES[options.domain] = options.style;
options.domain =
options.domain === 'mathspeak' ? 'clearspeak' : 'mathspeak';
options.style = EngineConst.DOMAIN_TO_STYLES[options.domain];
this.setOptions(options);
}

/**
* @override
*/
public nextStyle(id: string) {
this.setOption('style', this.nextStyle_(this.getRebuilt().nodeDict[id]));
}

/**
* Cycles to next style or preference of the speech rule set if possible.
*
* @param node The semantic node currently in focus.
* @returns The new style name.
*/
private nextStyle_(node: SemanticNode): string {
const {modality: modality, domain: domain, style: style} = this.getOptions();
// Rule cycling only makes sense for speech modality.
if (modality !== 'speech') {
return style;
}

if (domain === 'mathspeak') {
const styles = ['default', 'brief', 'sbrief'];
const index = styles.indexOf(style);
if (index === -1) {
return style;
}
return index >= styles.length - 1 ? styles[0] : styles[index + 1];
}
if (domain === 'clearspeak') {
const prefs = ClearspeakPreferences.getLocalePreferences();
const loc = prefs['en'];
// TODO: use correct locale.
if (!loc) {
return 'default';
}
// TODO: return the previous one?
const smart = ClearspeakPreferences.relevantPreferences(node);
const current = ClearspeakPreferences.findPreference(style, smart);
const options = loc[smart].map(function (x) {
return x.split('_')[1];
});
const index = options.indexOf(current);
if (index === -1) {
return style;
}
const next =
index >= options.length - 1 ? options[0] : options[index + 1];
const result = ClearspeakPreferences.addPreference(style, smart, next);
return result;
}
return style;
}

}
28 changes: 24 additions & 4 deletions ts/speech_generator/speech_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,45 @@ export interface SpeechGenerator {
*/
setRebuilt(rebuilt: RebuildStree): void;

/**
* Computes and sets the rebuilt element if it does not exist yet.
*
* @param xml The base xml element belonging to node.
* @param force Optional parameter to force recomputation of rebuilt.
* @returns The rebuilt semantic tree object.
*/
computeRebuilt(xml: Element, force?: boolean): RebuildStree;

/**
* Sets dynamic constraint options for the speech engine.
*
* @param options The dynamic constraint.
*/
setOptions(options: AxisMap): void;

/**
* Sets a single dynamic constraint option for the speech engine.
*
* @param key The key.
* @param value The value of the constraint.
*/
setOption(key: string, value: string): void;

/**
* @returns Dynamic constraint options of the generator.
*/
getOptions(): AxisMap;

/**
* Sets up or resets the speech generator.
* Cycles to next speech rule set if possible.
*/
start(): void;
nextRules(): void;

/**
* Cleans up after ending speech generation.
* Cycles to next style or preference of the speech rule set if possible.
*
* @param id Semantic Id of the currently focused node.
*/
end(): void;
nextStyle(id: string): void;

}
16 changes: 12 additions & 4 deletions ts/speech_generator/speech_generator_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ export function connectMactions(node: Element, mml: Element, stree: Element) {
// Set dummy type and id.
cspan.setAttribute(Attribute.TYPE, 'dummy');
cspan.setAttribute(Attribute.ID, mid);
cspan.setAttribute('role', 'treeitem');
cspan.setAttribute('aria-level', lchild.getAttribute('aria-level'));
// Indicate the alternative in the semantic tree.
const cst = DomUtil.querySelectorAllByAttrValue(stree, 'id', mid)[0];
cst.setAttribute('alternative', mid);
Expand Down Expand Up @@ -284,8 +286,10 @@ export function connectAllMactions(mml: Element, stree: Element) {
* @param node The XML node.
* @returns The summary speech string.
*/
export function retrieveSummary(node: Element): string {
const descrs = computeSummary(node);
export function retrieveSummary(
node: Element,
options: { [key: string]: string } = {}): string {
const descrs = computeSummary(node, options);
return AuralRendering.markup(descrs);
}

Expand All @@ -296,10 +300,14 @@ export function retrieveSummary(node: Element): string {
* @returns A list of auditory descriptions
* for the summary.
*/
function computeSummary(node: Element): AuditoryDescription[] {
function computeSummary(
node: Element,
options: { [key: string]: string } = {}): AuditoryDescription[] {
const preOption = options.locale ? {locale: options.locale} : {};
return node
? SpeechRuleEngine.getInstance().runInSetting(
{ modality: 'summary', strict: false, speech: true },
Object.assign(preOption,
{ modality: 'summary', strict: false, speech: true }),
function () {
return SpeechRuleEngine.getInstance().evaluateNode(node);
}
Expand Down
15 changes: 12 additions & 3 deletions ts/speech_generator/summary_speech_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@

import { AbstractSpeechGenerator } from './abstract_speech_generator.js';
import * as SpeechGeneratorUtil from './speech_generator_util.js';
import { setup as EngineSetup } from '../common/engine_setup.js';
import { Attribute } from '../enrich_mathml/enrich_attr.js';

export class SummarySpeechGenerator extends AbstractSpeechGenerator {

/**
* @override
*/
public getSpeech(node: Element, xml: Element) {
SpeechGeneratorUtil.connectAllMactions(xml, this.getRebuilt().xml);
return this.generateSpeech(node, xml);
public getSpeech(node: Element, _xml: Element) {
// SpeechGeneratorUtil.connectAllMactions(xml, this.getRebuilt().xml);
EngineSetup(this.getOptions());
const id = node.getAttribute(Attribute.ID);
const snode =
this.getRebuilt().streeRoot.querySelectorAll(x => x.id.toString() === id)[0];
SpeechGeneratorUtil.addModality(node, snode, this.modality);
const speech = node.getAttribute(Attribute.SUMMARY);
return speech;
}
}
3 changes: 3 additions & 0 deletions ts/speech_generator/tree_speech_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class TreeSpeechGenerator extends AbstractSpeechGenerator {
* @override
*/
public getSpeech(node: Element, xml: Element, root: Element = null) {
if (this.getRebuilt()) {
SpeechGeneratorUtil.connectMactions(node, xml, this.getRebuilt().xml);
}
const speech = this.generateSpeech(node, xml);
const nodes = this.getRebuilt().nodeDict;
for (const [key, snode] of Object.entries(nodes)) {
Expand Down
Loading

0 comments on commit 0719c81

Please sign in to comment.