blob: 83eb41aa138733a5d002e67d63364e62b554639e [file] [log] [blame]
// Copyright 2009 Google Inc. All Rights Reserved.
//
// 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.
/**
* @fileoverview Detector which looks for undeclared opposite-directionality
* text errors.
*/
goog.provide('bidichecker.UndeclaredTextDetector');
goog.require('bidichecker.Detector');
goog.require('bidichecker.DirChunk');
goog.require('bidichecker.DirChunkWalker');
goog.require('bidichecker.Error');
goog.require('bidichecker.Scanner');
goog.require('bidichecker.utils');
goog.require('goog.array');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
/**
* A detector which listens for {@code bidichecker.DirChunkEvent} events and
* checks for text with the opposite directionality as is declared for its
* chunk.
* @param {!bidichecker.ErrorCollector} errorCollector Collects any new errors
* discovered in this checking pass.
* @constructor
* @implements {bidichecker.Detector}
*/
bidichecker.UndeclaredTextDetector = function(errorCollector) {
/**
* @type {!bidichecker.ErrorCollector}
* @private
*/
this.errorCollector_ = errorCollector;
};
/** @inheritDoc */
bidichecker.UndeclaredTextDetector.prototype.startListening =
function(scanner) {
/**
* Service object to manage cleanup of event listeners.
* @type {goog.events.EventHandler}
*/
var eventHandler = new goog.events.EventHandler(this);
// Start listening for DirChunk events.
eventHandler.listen(scanner.getDirChunkWalker(),
bidichecker.DirChunkWalker.EventTypes.DIR_CHUNK,
this.handleChunk_);
// Listen for the end-of-DOM event; then call on event handler to stop
// listening to DOM events.
eventHandler.listenOnce(scanner.getDirChunkWalker(),
bidichecker.DirChunkWalker.EventTypes.END_OF_CHUNKS,
eventHandler.removeAll, false, eventHandler);
};
/**
* Checks whether the contents of a chunk of text have the same directionality
* as its context.
* @param {!goog.events.Event} event A {@code bidichecker.DirChunkEvent}.
* @private
*/
bidichecker.UndeclaredTextDetector.prototype.handleChunk_ = function(event) {
var self = this;
var chunk = event.target.getChunk();
if (chunk.isRtl()) {
var matches = bidichecker.utils.findLtrSubstrings(chunk.getText());
goog.array.forEach(matches, function(match) {
// LRMs may appear in an RTL context in order to fix spillover errors, as
// well as due to Hotshots, which encodes message ids using LRMs and RLMs.
// These intentional and valid uses of LRM/RLM should not be reported as
// errors.
if (!bidichecker.utils.hasOnlyLrmChars(match.text)) {
self.addError_(chunk, match, 'Undeclared LTR text');
}
});
} else {
var matches = bidichecker.utils.findRtlSubstrings(chunk.getText());
goog.array.forEach(matches, function(match) {
// As above, but for RLMs in an LTR context.
if (!bidichecker.utils.hasOnlyRlmChars(match.text)) {
self.addError_(chunk, match, 'Undeclared RTL text');
}
});
}
};
/**
* Adds a new error for undeclared text directionality.
* @param {!bidichecker.DirChunk} chunk The chunk where the error was found.
* @param {!bidichecker.utils.Substring} match The error location.
* @param {string} message The error message description prefix.
* @private
*/
bidichecker.UndeclaredTextDetector.prototype.addError_ = function(
chunk, match, message) {
// Undeclared text wrapped in a directionality declaration is unlikely to
// be a real error, since someone's clearly already handled it.
var severity = chunk.isDeclared() ? 4 : 3;
var highlightableArea =
chunk.getHighlightableArea(match.index, match.text.length);
var error =
new bidichecker.Error(message, severity, highlightableArea, match.text);
this.addAdjacentNeutrals_(chunk.getText(), match, error);
var locationElement =
(/** @type {Element} */ chunk.findNodeAtPosition(match.index).parentNode);
this.errorCollector_.addError(error, locationElement);
};
/**
* Modifies an undeclared-text error to take account of any adjacent neutral
* characters; they raise the severity of the error since they're likely to be
* garbled.
* @param {string} text The text of the chunk in which the error appears.
* @param {!bidichecker.utils.Substring} match The error location in the text.
* @param {!bidichecker.Error} error The original error object. The function
* modifies this object if adjacent neutral text is found.
* @private
*/
bidichecker.UndeclaredTextDetector.prototype.addAdjacentNeutrals_ = function(
text, match, error) {
var neutralsBefore =
bidichecker.utils.findNeutralTextBeforeIndex(text, match.index);
if (neutralsBefore) {
if (error.getSeverity() == 3) {
error.setSeverity(2);
}
error.setPrecededByText(neutralsBefore.text);
}
var neutralsAfter = bidichecker.utils.findNeutralTextAtIndex(
text, match.index + match.text.length);
if (neutralsAfter) {
if (error.getSeverity() == 3) {
error.setSeverity(2);
}
error.setFollowedByText(neutralsAfter.text);
}
};