| Index: third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js
|
| diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js b/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d414beaa6fdb370f336f92ddf1935a97c7ae5f97
|
| --- /dev/null
|
| +++ b/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js
|
| @@ -0,0 +1,423 @@
|
| +// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
|
| +// limitations under the License.
|
| +// See the License for the specific language governing permissions and
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// distributed under the License is distributed on an "AS-IS" BASIS,
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// You may obtain a copy of the License at
|
| +// you may not use this file except in compliance with the License.
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +//
|
| +// Copyright 2013 The ChromeOS VK Authors. 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 Defines the parsed layout object which will do layout parsing
|
| + * and expose the keymappings and the transforms to Model.
|
| + */
|
| +
|
| +goog.provide('i18n.input.chrome.vk.ParsedLayout');
|
| +
|
| +goog.require('goog.object');
|
| +goog.require('i18n.input.chrome.vk.KeyCode');
|
| +
|
| +
|
| +
|
| +/**
|
| + * Creates the parsed layout object per the raw layout info.
|
| + *
|
| + * @param {!Object} layout The raw layout object defined in the
|
| + * xxx_layout.js.
|
| + * @constructor
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout = function(layout) {
|
| + /**
|
| + * The layout code (a.k.a. id).
|
| + *
|
| + * @type {string}
|
| + */
|
| + this.id = layout['id'];
|
| +
|
| + /**
|
| + * The view object needed by UI rendering, including the key
|
| + * mappings. Some extra keys are not appear in following, which are
|
| + * '', 's', 'l', 'sl', 'cl', 'sc', 'scl'. They define the key mappings
|
| + * for each keyboard mode:
|
| + * '' means normal;
|
| + * 's' means SHIFT;
|
| + * 'l' means CAPSLOCK;
|
| + * 'c' means CTRL+ALT.
|
| + * Those modes will be filled when parsing the raw layout.
|
| + * If certain modes are not defined by the raw layout, this.view.<mode>
|
| + * won't be filled in.
|
| + * The mode format is: {
|
| + * '<keyChar>': ['<disp type(S|P)>', '<disp chars>', '<commit chars>']
|
| + * }.
|
| + *
|
| + * @type {!Object}
|
| + */
|
| + this.view = {
|
| + 'id': layout['id'],
|
| + 'title': layout['title'],
|
| + 'isRTL': layout['direction'] == 'rtl',
|
| + 'is102': !!layout['is102Keyboard'],
|
| + 'mappings': goog.object.create([
|
| + '', null,
|
| + 's', null,
|
| + 'c', null,
|
| + 'l', null,
|
| + 'sc', null,
|
| + 'cl', null,
|
| + 'sl', null,
|
| + 'scl', null
|
| + ])
|
| + };
|
| +
|
| + /**
|
| + * The parsed layout transforms. There are only 3 elements of this array.
|
| + * !st is the long exgexp to match, 2nd is the map of:
|
| + * <match location>: [<regexp>, <replacement>].
|
| + * 3rd/4th are the regexp for prefix matches.
|
| + *
|
| + * @type {Array.<!Object>}
|
| + */
|
| + this.transforms = null;
|
| +
|
| + /**
|
| + * The parsed layout ambiguous chars.
|
| + *
|
| + * @type {Object}
|
| + * @private
|
| + */
|
| + this.ambiRegex_ = null;
|
| +
|
| + // Parses the key mapping & transforms of the layout.
|
| + this.parseKeyMappings_(layout);
|
| + this.parseTransforms_(layout);
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Parses the key mappings of the given layout.
|
| + *
|
| + * @param {!Object} layout The raw layout object. It's format is:
|
| + * id: <layout id> in {string}
|
| + * title: <layout title> in {string}
|
| + * direction: 'rtl' or 'ltr'
|
| + * is102Keyboard: True if vk is 102, False/undefined for 101
|
| + * mappings: key map in {Object.<string,string>}
|
| + * '': keycodes (each char's charCode represents keycode) in normal state
|
| + * s: keycodes in SHIFT state
|
| + * c: keycodes in ALTGR state
|
| + * l: keycodes in CAPSLOCK state
|
| + * <the states could be combined, e.g. ',s,sc,sl,scl'>
|
| + * transform: in {Object.<string,string>}
|
| + * <regexp>: <replacement>
|
| + * historyPruneRegex: <regexp string to represent the ambiguities>.
|
| + * @private
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout.prototype.parseKeyMappings_ = function(
|
| + layout) {
|
| + var codes = this.view['is102'] ? i18n.input.chrome.vk.KeyCode.CODES102 :
|
| + i18n.input.chrome.vk.KeyCode.CODES101;
|
| +
|
| + var mappings = layout['mappings'];
|
| + for (var m in mappings) {
|
| + var map = mappings[m];
|
| + var modes = m.split(/,/);
|
| + if (modes.join(',') != m) {
|
| + modes.push(''); // IE splits 'a,b,' into ['a','b']
|
| + }
|
| + var parsed = {};
|
| + // Example for map is like:
|
| + // 1) {'': '\u00c0123456...', ...}
|
| + // 2) {'QWERT': 'QWERT', ...}
|
| + // 3) {'A': 'aa', ...}
|
| + // 4) {'BCD': '{{bb}}cd', ...}
|
| + // 5) {'EFG': '{{S||e||ee}}FG', ...}
|
| + // 6) {'HI': '{{P||12||H}}i', ...}
|
| + for (var from in map) {
|
| + // In case #1, from is '', to is '\u00c0123456...'.
|
| + // In case #3, from is 'A', to is 'aa'.
|
| + var to = map[from];
|
| + if (from == '') {
|
| + from = codes;
|
| + // If is 102 keyboard, modify 'to' to be compatible with the old vk.
|
| + if (this.view['is102']) {
|
| + // Moves the 26th char {\} to be the 38th char (after {'}).
|
| + var normalizedTo = to.slice(0, 25);
|
| + normalizedTo += to.slice(26, 37);
|
| + normalizedTo += to.charAt(25);
|
| + normalizedTo += to.slice(37);
|
| + to = normalizedTo;
|
| + }
|
| + }
|
| + // Replaces some chars for backward compatibility to old layout
|
| + // definitions.
|
| + from = from.replace('m', '\u00bd');
|
| + from = from.replace('=', '\u00bb');
|
| + from = from.replace(';', '\u00ba');
|
| + if (from.length == 1) {
|
| + // Case #3: single char map to chars.
|
| + parsed[from] = ['S', to, to];
|
| + } else {
|
| + var j = 0;
|
| + for (var i = 0, c; c = from.charAt(i); ++i) {
|
| + var t = to.charAt(j++);
|
| + if (t == to.charAt(j) && t == '{') {
|
| + // Case #4/5/6: {{}} to define single char map to chars.
|
| + var k = to.indexOf('}}', j);
|
| + if (k < j) break;
|
| + var s = to.slice(j + 1, k);
|
| + var parts = s.split('||');
|
| + if (parts.length == 3) {
|
| + // Case #5/6: button/commit chars seperation.
|
| + parsed[c] = parts;
|
| + } else if (parts.length == 1) {
|
| + // Case #4.
|
| + parsed[c] = ['S', s, s];
|
| + }
|
| + j = k + 2;
|
| + } else {
|
| + // Normal case: single char map to according single char.
|
| + parsed[c] = ['S', t, t];
|
| + }
|
| + }
|
| + }
|
| + }
|
| + for (var i = 0, mode; mode = modes[i], mode != undefined; ++i) {
|
| + this.view['mappings'][mode] = parsed;
|
| + }
|
| + }
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Prefixalizes the regexp string.
|
| + *
|
| + * @param {string} re_str The original regexp string.
|
| + * @return {string} The prefixalized the regexp string.
|
| + * @private
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout.prototype.prefixalizeRegexString_ = function(
|
| + re_str) {
|
| + // Makes sure [...\[\]...] won't impact the later replaces.
|
| + re_str = re_str.replace(/\\./g, function(m) {
|
| + if (/^\\\[/.test(m)) {
|
| + return '\u0001';
|
| + }
|
| + if (/^\\\]/.test(m)) {
|
| + return '\u0002';
|
| + }
|
| + return m;
|
| + });
|
| + // Prefixalizes.
|
| + re_str = re_str.replace(/\\.|\[[^\[\]]*\]|\{.*\}|[^\|\\\(\)\[\]\{\}\*\+\?]/g,
|
| + function(m) {
|
| + if (/^\{/.test(m)) {
|
| + return m;
|
| + }
|
| + return '(?:' + m + '|$)';
|
| + });
|
| + // Restores the \[\].
|
| + re_str = re_str.replace(/\u0001/g, '\\[');
|
| + re_str = re_str.replace(/\u0002/g, '\\]');
|
| + return re_str;
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Parses the transforms of the given layout.
|
| + *
|
| + * @param {!Object} layout The raw layout object. It's format is:
|
| + * id: <layout id> in {string}
|
| + * title: <layout title> in {string}
|
| + * direction: 'rtl' or 'ltr'
|
| + * is102Keyboard: True if vk is 102, False/undefined for 101
|
| + * mappings: key map in {Object.<string,string>}
|
| + * '': keycodes (each char's charCode represents keycode) in normal state
|
| + * s: keycodes in SHIFT state
|
| + * c: keycodes in ALTGR state
|
| + * l: keycodes in CAPSLOCK state
|
| + * <the states could be combined, e.g. ',s,sc,sl,scl'>
|
| + * transform: in {Object.<string,string>}
|
| + * <regexp>: <replacement>
|
| + * historyPruneRegex: <regexp string to represent the ambiguities>.
|
| + * @private
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout.prototype.parseTransforms_ = function(
|
| + layout) {
|
| + var transforms = layout['transform'];
|
| + if (transforms) {
|
| + // regobjs is RegExp objects of the regexp string.
|
| + // regexsalone will be used to get the long regexp which concats all the
|
| + // transform regexp as (...$)|(...$)|...
|
| + // The long regexp is needed because it is ineffecient to match each regexp
|
| + // one by one. Instead, we match the long regexp only once. But we need to
|
| + // know where the match happens and which replacement we need to use.
|
| + // So regobjs will hold the map between the match location and the
|
| + // regexp/replacement.
|
| + var regobjs = [], regexesalone = [], partialRegexs = [];
|
| + // sum_numgrps is the index of current reg group for future matching.
|
| + // Don't care about the whole string in array index 0.
|
| + var sum_numgrps = 1;
|
| + for (var regex in transforms) {
|
| + var regobj = new RegExp(regex + '$');
|
| + var repl = transforms[regex];
|
| + regobjs[sum_numgrps] = [regobj, repl];
|
| + regexesalone.push('(' + regex + '$)');
|
| + partialRegexs.push('^(' + this.prefixalizeRegexString_(regex) + ')');
|
| + // The match should happen to count braces.
|
| + var grpCountRegexp = new RegExp(regex + '|.*');
|
| + // The length attribute would count whole string as well.
|
| + // However, that extra count 1 is compensated by
|
| + // extra braces added.
|
| + var numgrps = grpCountRegexp.exec('').length;
|
| + sum_numgrps += numgrps;
|
| + }
|
| + var longregobj = new RegExp(regexesalone.join('|'));
|
| + // Saves 2 long regexp objects for later prefix matching.
|
| + // The reason to save a regexp with '\u0001' is to make sure the whole
|
| + // string won't match as a prefix for the whole pattern. For example,
|
| + // 'abc' shouldn't match /abc/.
|
| + // In above case, /abc/ is prefixalized as re = /(a|$)(b|$)(c|$)/.
|
| + // 'a', 'ab' & 'abc' can all match re.
|
| + // So make another re2 = /(a|$)(b|$)(c|$)\u0001/, therefore, 'abc' will
|
| + // fail to match. Finally, we can use this checks to make sure the prefix
|
| + // match: "s matches re but it doesn't match re2".
|
| + var prefixregobj = new RegExp(partialRegexs.join('|'));
|
| + // Uses reverse-ordered regexp for prefix matching. Details are explained
|
| + // in predictTransform().
|
| + var prefixregobj2 = new RegExp(partialRegexs.reverse().join('|'));
|
| + this.transforms = [longregobj, regobjs, prefixregobj, prefixregobj2];
|
| + }
|
| +
|
| + var hisPruReg = layout['historyPruneRegex'];
|
| + if (hisPruReg) {
|
| + this.ambiRegex_ = new RegExp('^(' + hisPruReg + ')$');
|
| + }
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Predicts whether there would be future transforms for the given string.
|
| + *
|
| + * @param {string} text The given string.
|
| + * @return {number} The matched position in the string. Returns -1 for no match.
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout.prototype.predictTransform = function(text) {
|
| + if (!this.transforms || !text) {
|
| + return -1;
|
| + }
|
| + for (var i = 0; i < text.length; i++) {
|
| + var s = text.slice(i - text.length);
|
| + // Uses multiple mathches to make sure the prefix match.
|
| + // Refers to comments in parseTransforms_() method.
|
| + var matches = s.match(this.transforms[2]);
|
| + if (matches && matches[0]) {
|
| + for (var j = 1; j < matches.length && !matches[j]; j++) {}
|
| + var matchedIndex = j;
|
| + // Ties to match the reversed regexp and see whether the matched indexes
|
| + // are pointed to the same rule.
|
| + matches = s.match(this.transforms[3]);
|
| + if (matches && matches[0]) { // This should always match!
|
| + for (var j = 1; j < matches.length && !matches[j]; j++) {}
|
| + if (matchedIndex != matches.length - j) {
|
| + // If the matched and reverse-matched index are not the same, it
|
| + // means the string must be a prefix, because the layout transforms
|
| + // shouldn't have duplicated transforms.
|
| + return i;
|
| + } else {
|
| + // Gets the matched rule regexp, and revise it to add a never-matched
|
| + // char X in the end. And tries to match it with s+X.
|
| + // If matched, it means the s is a full match instead of a prefix
|
| + // match.
|
| + var re = this.transforms[1][matchedIndex][0];
|
| + re = new RegExp(re.toString().match(/\/(.*)\//)[1] + '\u0001');
|
| + if (!(s + '\u0001').match(re)) {
|
| + return i;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return -1;
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Applies the layout transform and gets the result.
|
| + *
|
| + * @param {string} prevstr The previous text.
|
| + * @param {number} transat The position of previous transform. If it's -1,
|
| + * it means no transform happened.
|
| + * @param {string} ch The new chars currently added to prevstr.
|
| + * @return {Object} The transform result. It's format is:
|
| + * {back: <the number of chars to be deleted in the end of the prevstr>,
|
| + * chars: <the chars to add at the tail after the deletion>}.
|
| + * If there is no transform applies, return null.
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout.prototype.transform = function(
|
| + prevstr, transat, ch) {
|
| + if (!this.transforms) return null;
|
| +
|
| + var str;
|
| + if (transat > 0) {
|
| + str = prevstr.slice(0, transat) + '\u001d' +
|
| + prevstr.slice(transat) + ch;
|
| + } else {
|
| + str = prevstr + ch;
|
| + }
|
| + var longr = this.transforms[0];
|
| + var matchArr = longr.exec(str);
|
| + if (matchArr) {
|
| + var rs = this.transforms[1];
|
| +
|
| + for (var i = 1; i < matchArr.length && !matchArr[i]; i++) {}
|
| + var matchGroup = i;
|
| +
|
| + var regobj = rs[matchGroup][0];
|
| + var repl = rs[matchGroup][1];
|
| + var m = regobj.exec(str);
|
| +
|
| + // String visible to user does not have LOOK_BEHIND_SEP_ and chars.
|
| + // So need to discount them in backspace count.
|
| + var rmstr = str.slice(m.index);
|
| + var numseps = rmstr.search('\u001d') > -1 ? 1 : 0;
|
| + var backlen = rmstr.length - numseps - ch.length;
|
| +
|
| + var newstr = str.replace(regobj, repl);
|
| + var replstr = newstr.slice(m.index);
|
| + replstr = replstr.replace('\u001d', '');
|
| +
|
| + return {back: backlen, chars: replstr};
|
| + }
|
| +
|
| + return null;
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Gets whether the given chars is ambiguious chars.
|
| + *
|
| + * @param {string} chars The chars to be judged.
|
| + * @return {boolean} True if given chars is ambiguious chars, false
|
| + * otherwise.
|
| + */
|
| +i18n.input.chrome.vk.ParsedLayout.prototype.isAmbiChars = function(chars) {
|
| + return this.ambiRegex_ ? !!this.ambiRegex_.exec(chars) : false;
|
| +};
|
|
|