Skip to content

Commit

Permalink
FLUID-6175: Moved parsing domReader parsing to a subcomponent.
Browse files Browse the repository at this point in the history
Moved the parsing into the fluid.orator.domReader.parser grade so that
it could better facilitate re-use and configuration.
  • Loading branch information
jobara committed Jul 6, 2018
1 parent 399439c commit 9fc925e
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 143 deletions.
260 changes: 143 additions & 117 deletions src/components/orator/js/Orator.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
}
}
},
components: {
parser: {
type: "fluid.orator.domReader.parser"
}
},
invokers: {
readFromDOM: {
funcName: "fluid.orator.domReader.readFromDOM",
Expand Down Expand Up @@ -429,8 +434,129 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
that.parseIndex = 0;
};

/**
* Combines the parsed text into a String.
*
* @param {Array} parsed - An array of parsed data points
*
* @return {String} - The parsed text combined into a String.
*/
fluid.orator.domReader.parsedToString = function (parsed) {
var words = fluid.transform(parsed, function (block) {
return block.word;
});

return words.join("");
};

/**
* Parses the DOM element into data points to use for highlighting the text, and queues the text into the self
* voicing engine. The parsed data points are added to the component's `parseQueue`
*
* @param {Component} that - the component
* @param {String|jQuery|DomElement} elm - The DOM node to read
*/
fluid.orator.domReader.readFromDOM = function (that, elm) {
elm = $(elm);

// only execute if there are nodes to read from
if (elm.length) {
var parsedFromElm = that.parser.parse(elm[0]);
that.parseQueue = parsedFromElm;
that.events.onReadFromDOM.fire(parsedFromElm);
that.queueSpeech(fluid.orator.domReader.parsedToString(parsedFromElm));
}
};

/**
* Returns the index of the closest data point from the parseQueue based on the boundary provided.
*
* @param {Array} parseQueue - An array data points generated from parsing a DOM structure
* @param {Integer} currentIndex - The index into the paraseQueue to start searching from. The currentIndex will be
* constrained to the bounds of the parseQueue.
* @param {Integer} boundary - The boundary value used to compare against the blockIndex of the parsed data points.
*
* @return {Integer|undefined} - Will return the index of the closest data point in the parseQueue. If the boundary
* cannot be located within the parseQueue, `undefined` is returned.
*/
fluid.orator.domReader.getClosestIndex = function (parseQueue, currentIndex, boundary) {
var maxIdx = Math.max(parseQueue.length - 1, 0);
currentIndex = Math.max(Math.min(currentIndex, maxIdx), 0);

var nextIdx = currentIndex + 1;
var prevIdx = currentIndex - 1;

var currentBlockIndex = parseQueue[currentIndex].blockIndex;
var maxBoundary = parseQueue[maxIdx].blockIndex + parseQueue[maxIdx].word.length;


if (!fluid.isValue(boundary) || boundary < 0 || boundary > maxBoundary ) {
return undefined;
}

if (currentBlockIndex > boundary) {
return fluid.orator.domReader.getClosestIndex(parseQueue, prevIdx, boundary);
}

var isInNextBound = parseQueue[nextIdx] ? boundary < parseQueue[nextIdx].blockIndex : boundary <= maxBoundary;

if (currentBlockIndex === boundary || (currentIndex <= maxIdx && isInNextBound)) {
return currentIndex;
}

return fluid.orator.domReader.getClosestIndex(parseQueue, nextIdx, boundary);
};

/**
* Highlights text from the parseQueue according to the specified boundary. Highlights are performed by wrapping
* the appropriate text in the markup specified by `that.options.markup.highlight`.
*
* @param {Component} that - the component
* @param {Integer} boundary - the boundary point used to find the text to highlight. Typically this is the
* utterance boundary returned from the utteranceOnBoundary event.
*/
fluid.orator.domReader.highlight = function (that, boundary) {
that.removeHighlight();

if (that.parseQueue.length) {
var closestIndex = fluid.orator.domReader.getClosestIndex(that.parseQueue, that.parseIndex, boundary);

if (fluid.isValue(closestIndex)) {
that.parseIndex = closestIndex;

var data = that.parseQueue[that.parseIndex];
var rangeNode = data.parentNode.childNodes[data.childIndex];

that.range.selectNode(rangeNode);
that.range.setStart(rangeNode, data.startOffset);
that.range.setEnd(rangeNode, data.endOffset);
that.range.surroundContents($(that.options.markup.highlight)[0]);
}
}
};

/*******************************************************************************
* fluid.orator.domReader.parser
*
* Parses the text from the DOM into a format that is suitable for reading back
* by the fluid.orator.domReader
*******************************************************************************/

fluid.defaults("fluid.orator.domReader.parser", {
gradeNames: ["fluid.component"],
invokers: {
parse: {
funcName: "fluid.orator.domReader.parser.parse",
args: ["{that}", "{arguments}.0", "{arguments}.1"]
},
hasTextToRead: "fluid.orator.domReader.parser.hasTextToRead",
isWord: "fluid.orator.domReader.parser.isWord",
addParsedData: "fluid.orator.domReader.parser.addParsedData"
}
});

// Constants representing DOM node types.
fluid.orator.domReader.nodeType = {
fluid.orator.domReader.parser.nodeType = {
ELEMENT_NODE: 1,
TEXT_NODE: 3
};
Expand All @@ -443,7 +569,7 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
*
* @return {Boolean} - `true` if a word, `false` otherwise.
*/
fluid.orator.domReader.isWord = function (str) {
fluid.orator.domReader.parser.isWord = function (str) {
return fluid.isValue(str) && /\S/.test(str);
};

Expand All @@ -464,12 +590,12 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
* @return {Boolean} - returns true if there is rendered text within the element and false otherwise.
* (See rules above)
*/
fluid.orator.domReader.hasTextToRead = function (elm) {
fluid.orator.domReader.parser.hasTextToRead = function (elm) {
elm = fluid.unwrap(elm);

return elm &&
!!elm.offsetHeight &&
fluid.orator.domReader.isWord(elm.innerText) &&
fluid.orator.domReader.parser.isWord(elm.innerText) &&
!$(elm).closest("[aria-hidden=\"true\"]").length;
};

Expand All @@ -495,7 +621,7 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
* word in the string representing the text of the node.
* @param {Integer} childIndex - The index of the node in the list of its parent's child nodes.
*/
fluid.orator.domReader.addParsedData = function (parsed, word, childNode, blockIndex, charIndex, childIndex) {
fluid.orator.domReader.parser.addParsedData = function (parsed, word, childNode, blockIndex, charIndex, childIndex) {
parsed.push({
blockIndex: blockIndex,
startOffset: charIndex,
Expand All @@ -514,41 +640,42 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
* NOTE: consecutive whitespace is collapsed to the first whitespace character.
* NOTE: hidden text is skipped.
*
* @param {Component} that - the component
* @param {jQuery|DomElement} elm - the DOM node to parse
* @param {Integer} blockIndex - The `blockIndex` represents the index into the entire block of text being parsed.
* It defaults to 0 and is primarily used internally for recursive calls.
*
* @return {ParseQueue[]} - An array of data points for the Parsed DOM
* See fluid.orator.domReader.addParsedData for details on the structure.
* See fluid.orator.domReader.parser.addParsedData for details on the structure.
*/
fluid.orator.domReader.parse = function (elm, blockIndex) {
fluid.orator.domReader.parser.parse = function (that, elm, blockIndex) {
var parsed = [];
elm = fluid.unwrap(elm);
blockIndex = blockIndex || 0;

if (fluid.orator.domReader.hasTextToRead(elm)) {
if (that.hasTextToRead(elm)) {
var childNodes = elm.childNodes;

$.each(childNodes, function (childIndex, childNode) {
if (childNode.nodeType === fluid.orator.domReader.nodeType.TEXT_NODE) {
if (childNode.nodeType === fluid.orator.domReader.parser.nodeType.TEXT_NODE) {
var words = childNode.textContent.split(/(\s+)/); // split on whitespace, and capture whitespace
// charIndex is the start index of the word in the nested block of text
var charIndex = 0;

fluid.each(words, function (word) {
if (fluid.orator.domReader.isWord(word)) {
fluid.orator.domReader.addParsedData(parsed, word, childNode, blockIndex, charIndex, childIndex);
if (that.isWord(word)) {
that.addParsedData(parsed, word, childNode, blockIndex, charIndex, childIndex);
blockIndex += word.length;
// if the current `word` is not an empty string and the last parsed `word` is not whitespace
} else if (word && fluid.orator.domReader.isWord(fluid.get(parsed, [(parsed.length - 1), "word"]))) {
fluid.orator.domReader.addParsedData(parsed, word, childNode, blockIndex, charIndex, childIndex);
} else if (word && that.isWord(fluid.get(parsed, [(parsed.length - 1), "word"]))) {
that.addParsedData(parsed, word, childNode, blockIndex, charIndex, childIndex);
blockIndex += word.length;
}
charIndex += word.length;
});
} else if (childNode.nodeType === fluid.orator.domReader.nodeType.ELEMENT_NODE &&
fluid.orator.domReader.hasTextToRead(childNode)) {
parsed = parsed.concat(fluid.orator.domReader.parse(childNode, blockIndex));
} else if (childNode.nodeType === fluid.orator.domReader.parser.nodeType.ELEMENT_NODE &&
that.hasTextToRead(childNode)) {
parsed = parsed.concat(fluid.orator.domReader.parser.parse(that, childNode, blockIndex));
if (parsed.length) {
var lastParsed = parsed[parsed.length - 1];
blockIndex = lastParsed.blockIndex + lastParsed.word.length;
Expand All @@ -560,107 +687,6 @@ var fluid_3_0_0 = fluid_3_0_0 || {};
return parsed;
};

/**
* Combines the parsed text into a String.
*
* @param {Array} parsed - An array of parsed data points
*
* @return {String} - The parsed text combined into a String.
*/
fluid.orator.domReader.parsedToString = function (parsed) {
var words = fluid.transform(parsed, function (block) {
return block.word;
});

return words.join("");
};

/**
* Parses the DOM element into data points to use for highlighting the text, and queues the text into the self
* voicing engine. The parsed data points are added to the component's `parseQueue`
*
* @param {Component} that - the component
* @param {String|jQuery|DomElement} elm - The DOM node to read
*/
fluid.orator.domReader.readFromDOM = function (that, elm) {
elm = $(elm);

// only execute if there are nodes to read from
if (elm.length) {
var parsedFromElm = fluid.orator.domReader.parse(elm[0]);
that.parseQueue = parsedFromElm;
that.events.onReadFromDOM.fire(parsedFromElm);
that.queueSpeech(fluid.orator.domReader.parsedToString(parsedFromElm));
}
};

/**
* Returns the index of the closest data point from the parseQueue based on the boundary provided.
*
* @param {Array} parseQueue - An array data points generated from parsing a DOM structure
* @param {Integer} currentIndex - The index into the paraseQueue to start searching from. The currentIndex will be
* constrained to the bounds of the parseQueue.
* @param {Integer} boundary - The boundary value used to compare against the blockIndex of the parsed data points.
*
* @return {Integer|undefined} - Will return the index of the closest data point in the parseQueue. If the boundary
* cannot be located within the parseQueue, `undefined` is returned.
*/
fluid.orator.domReader.getClosestIndex = function (parseQueue, currentIndex, boundary) {
var maxIdx = Math.max(parseQueue.length - 1, 0);
currentIndex = Math.max(Math.min(currentIndex, maxIdx), 0);

var nextIdx = currentIndex + 1;
var prevIdx = currentIndex - 1;

var currentBlockIndex = parseQueue[currentIndex].blockIndex;
var maxBoundary = parseQueue[maxIdx].blockIndex + parseQueue[maxIdx].word.length;


if (!fluid.isValue(boundary) || boundary < 0 || boundary > maxBoundary ) {
return undefined;
}

if (currentBlockIndex > boundary) {
return fluid.orator.domReader.getClosestIndex(parseQueue, prevIdx, boundary);
}

var isInNextBound = parseQueue[nextIdx] ? boundary < parseQueue[nextIdx].blockIndex : boundary <= maxBoundary;

if (currentBlockIndex === boundary || (currentIndex <= maxIdx && isInNextBound)) {
return currentIndex;
}

return fluid.orator.domReader.getClosestIndex(parseQueue, nextIdx, boundary);
};

/**
* Highlights text from the parseQueue according to the specified boundary. Highlights are performed by wrapping
* the appropriate text in the markup specified by `that.options.markup.highlight`.
*
* @param {Component} that - the component
* @param {Integer} boundary - the boundary point used to find the text to highlight. Typically this is the
* utterance boundary returned from the utteranceOnBoundary event.
*/
fluid.orator.domReader.highlight = function (that, boundary) {
that.removeHighlight();

if (that.parseQueue.length) {
var closestIndex = fluid.orator.domReader.getClosestIndex(that.parseQueue, that.parseIndex, boundary);

if (fluid.isValue(closestIndex)) {
that.parseIndex = closestIndex;

var data = that.parseQueue[that.parseIndex];
var rangeNode = data.parentNode.childNodes[data.childIndex];

that.range.selectNode(rangeNode);
that.range.setStart(rangeNode, data.startOffset);
that.range.setEnd(rangeNode, data.endOffset);
that.range.surroundContents($(that.options.markup.highlight)[0]);
}
}
};



/*******************************************************************************
Expand Down
Loading

0 comments on commit 9fc925e

Please sign in to comment.