diff options
Diffstat (limited to 'java/src')
7 files changed, 110 insertions, 112 deletions
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java index 3247997f6..938388d6c 100644 --- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java @@ -39,30 +39,30 @@ public class EditorInfoCompatUtils { private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS); + // EditorInfo.IME_FLAG_NAVIGATE_NEXT has been introduced since API#11 (Honeycomb). public static boolean hasFlagNavigateNext(int imeOptions) { if (OBJ_IME_FLAG_NAVIGATE_NEXT == null) return false; return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0; } + // EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS has been introduced since API#11 (Honeycomb). public static boolean hasFlagNavigatePrevious(int imeOptions) { if (OBJ_IME_FLAG_NAVIGATE_PREVIOUS == null) return false; return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0; } + // EditorInfo.IME_FLAG_FORCE_ASCII has been introduced since API#16 (JellyBean). public static boolean hasFlagForceAscii(int imeOptions) { if (OBJ_IME_FLAG_FORCE_ASCII == null) return false; return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0; } - public static void performEditorActionNext(InputConnection ic) { - ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); - } - + // EditorInfo.IME_ACTION_PREVIOUS has been introduced since API#11 (Honeycomb). public static void performEditorActionPrevious(InputConnection ic) { - if (OBJ_IME_ACTION_PREVIOUS == null) + if (OBJ_IME_ACTION_PREVIOUS == null || ic == null) return; ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS); } diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 60e506914..30ed59e18 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -99,8 +99,10 @@ public class Keyboard { public static final int CODE_SETTINGS = -5; public static final int CODE_SHORTCUT = -6; public static final int CODE_ACTION_ENTER = -7; + public static final int CODE_ACTION_NEXT = -8; + public static final int CODE_ACTION_PREVIOUS = -9; // Code value representing the code is not specified. - public static final int CODE_UNSPECIFIED = -9; + public static final int CODE_UNSPECIFIED = -10; public final KeyboardId mId; public final int mThemeId; @@ -381,6 +383,8 @@ public class Keyboard { case CODE_SETTINGS: return "settings"; case CODE_SHORTCUT: return "shortcut"; case CODE_ACTION_ENTER: return "actionEnter"; + case CODE_ACTION_NEXT: return "actionNext"; + case CODE_ACTION_PREVIOUS: return "actionPrevious"; case CODE_UNSPECIFIED: return "unspec"; case CODE_TAB: return "tab"; case CODE_ENTER: return "enter"; @@ -1069,8 +1073,10 @@ public class Keyboard { KeyboardId.elementIdToName(id.mElementId)); final boolean modeMatched = matchTypedValue(a, R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); - final boolean navigateActionMatched = matchBoolean(a, - R.styleable.Keyboard_Case_navigateAction, id.navigateAction()); + final boolean navigateNextMatched = matchBoolean(a, + R.styleable.Keyboard_Case_navigateNext, id.navigateNext()); + final boolean navigatePreviousMatched = matchBoolean(a, + R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious()); final boolean passwordInputMatched = matchBoolean(a, R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); final boolean clobberSettingsKeyMatched = matchBoolean(a, @@ -1090,30 +1096,32 @@ public class Keyboard { final boolean countryCodeMatched = matchString(a, R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); final boolean selected = keyboardSetElementMatched && modeMatched - && navigateActionMatched && passwordInputMatched + && navigateNextMatched && navigatePreviousMatched && passwordInputMatched && clobberSettingsKeyMatched && shortcutKeyEnabledMatched && hasShortcutKeyMatched && isMultiLineMatched && imeActionMatched && localeCodeMatched && languageCodeMatched && countryCodeMatched; if (DEBUG) { - startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, + startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement), "keyboardSetElement"), textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), - booleanAttr(a, R.styleable.Keyboard_Case_navigateAction, - "navigateAction"), - booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, - "passwordInput"), + textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), + "imeAction"), + booleanAttr(a, R.styleable.Keyboard_Case_navigateNext, + "navigateNext"), + booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious, + "navigatePrevious"), booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey, "clobberSettingsKey"), + booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, + "passwordInput"), booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, "shortcutKeyEnabled"), booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, "hasShortcutKey"), booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine, "isMultiLine"), - textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), - "imeAction"), textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), "localeCode"), textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index a75caf262..ed4a89e0f 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -87,7 +87,6 @@ public class KeyboardId { id.mElementId, id.mMode, id.mWidth, - id.navigateAction(), id.passwordInput(), id.mClobberSettingsKey, id.mShortcutKeyEnabled, @@ -95,6 +94,8 @@ public class KeyboardId { id.isMultiLine(), id.imeAction(), id.mCustomActionLabel, + id.navigateNext(), + id.navigatePrevious(), id.mLocale }); } @@ -106,7 +107,6 @@ public class KeyboardId { && other.mElementId == this.mElementId && other.mMode == this.mMode && other.mWidth == this.mWidth - && other.navigateAction() == this.navigateAction() && other.passwordInput() == this.passwordInput() && other.mClobberSettingsKey == this.mClobberSettingsKey && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled @@ -114,6 +114,8 @@ public class KeyboardId { && other.isMultiLine() == this.isMultiLine() && other.imeAction() == this.imeAction() && TextUtils.equals(other.mCustomActionLabel, this.mCustomActionLabel) + && other.navigateNext() == this.navigateNext() + && other.navigatePrevious() == this.navigatePrevious() && other.mLocale.equals(this.mLocale); } @@ -146,12 +148,12 @@ public class KeyboardId { return mElementId == ELEMENT_PHONE_SYMBOLS; } - public boolean navigateAction() { - // Note: Turn off checking navigation flag to show TAB key for now. - boolean navigateAction = InputTypeCompatUtils.isWebInputType(mEditorInfo.inputType); -// || EditorInfoCompatUtils.hasFlagNavigateNext(mImeOptions) -// || EditorInfoCompatUtils.hasFlagNavigatePrevious(mImeOptions); - return navigateAction; + public boolean navigateNext() { + return EditorInfoCompatUtils.hasFlagNavigateNext(mEditorInfo.imeOptions); + } + + public boolean navigatePrevious() { + return EditorInfoCompatUtils.hasFlagNavigatePrevious(mEditorInfo.imeOptions); } public boolean passwordInput() { @@ -165,15 +167,21 @@ public class KeyboardId { } public int imeAction() { + final int actionId = mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; if ((mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { return EditorInfo.IME_ACTION_NONE; } else if (mEditorInfo.actionLabel != null) { return IME_ACTION_CUSTOM_LABEL; } else { - return mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; + return actionId; } } + public int imeActionId() { + final int actionId = imeAction(); + return actionId == IME_ACTION_CUSTOM_LABEL ? mEditorInfo.actionId : actionId; + } + @Override public boolean equals(Object other) { return other instanceof KeyboardId && equals((KeyboardId) other); @@ -186,17 +194,19 @@ public class KeyboardId { @Override public String toString() { - return String.format("[%s %s %s%d %s %s %s%s%s%s%s]", + return String.format("[%s %s %s%d %s %s %s%s%s%s%s%s%s]", elementIdToName(mElementId), mLocale, (mOrientation == 1 ? "port" : "land"), mWidth, modeName(mMode), imeAction(), + (navigateNext() ? "navigateNext" : ""), + (navigatePrevious() ? "navigatePrevious" : ""), (mClobberSettingsKey ? " clobberSettingsKey" : ""), - (navigateAction() ? " navigateAction" : ""), (passwordInput() ? " passwordInput" : ""), (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""), - (mHasShortcutKey ? " hasShortcutKey" : "") + (mHasShortcutKey ? " hasShortcutKey" : ""), + (isMultiLine() ? "isMultiLine" : "") ); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java index 12a9c51f2..9e5c227eb 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java @@ -163,10 +163,10 @@ public class KeyStyles { if (DEBUG) { Log.d(TAG, String.format("<%s styleName=%s />", Keyboard.Builder.TAG_KEY_STYLE, styleName)); - } - if (mStyles.containsKey(styleName)) { - throw new XmlParseUtils.ParseException( - "duplicate key style declared: " + styleName, parser); + if (mStyles.containsKey(styleName)) { + Log.d(TAG, "key-style " + styleName + " is overridden at " + + parser.getPositionDescription()); + } } final DeclaredKeyStyle style = new DeclaredKeyStyle(); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ce306eaad..a208a8748 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -165,8 +165,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Double space: the state where the user pressed space twice quickly, which LatinIME // resolved as period-space. Undoing this converts the period to a space. private static final int SPACE_STATE_DOUBLE = 1; - // Swap punctuation: the state where a (weak or magic) space and a punctuation from the - // suggestion strip have just been swapped. Undoing this swaps them back. + // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip + // have just been swapped. Undoing this swaps them back; the space is still considered weak. private static final int SPACE_STATE_SWAP_PUNCTUATION = 2; // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak // spaces happen when the user presses space, accepting the current suggestion (whether @@ -887,26 +887,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // the second one - the first call successfully avoids this test, but the second one // enters. For the moment we rely on noComposingSpan to further reduce the impact. + // TODO: the following is probably better done in resetEntireInputState(). + // it should only happen when the cursor moved, and the very purpose of the + // test below is to narrow down whether this happened or not. Likewise with + // the call to postUpdateShiftState. // We set this to NONE because after a cursor move, we don't want the space // state-related special processing to kick in. mSpaceState = SPACE_STATE_NONE; - if (((mWordComposer.isComposingWord()) - || mVoiceProxy.isVoiceInputHighlighted()) - && (selectionChanged || noComposingSpan)) { - resetComposingState(true /* alsoResetLastComposedWord */); - updateSuggestions(); - final InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ic.finishComposingText(); - } - mComposingStateManager.onFinishComposingText(); - mVoiceProxy.setVoiceInputHighlighted(false); - } else if (!mWordComposer.isComposingWord()) { - // TODO: is the following reset still needed, given that we are not composing - // a word? - resetComposingState(true /* alsoResetLastComposedWord */); - updateSuggestions(); + if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) { + resetEntireInputState(); } mHandler.postUpdateShiftState(); @@ -1116,6 +1106,20 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return super.onKeyUp(keyCode, event); } + // This will reset the whole input state to the starting state. It will clear + // the composing word, reset the last composed word, tell the inputconnection + // and the composingStateManager about it. + private void resetEntireInputState() { + resetComposingState(true /* alsoResetLastComposedWord */); + updateSuggestions(); + final InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } + mComposingStateManager.onFinishComposingText(); + mVoiceProxy.setVoiceInputHighlighted(false); + } + private void resetComposingState(final boolean alsoResetLastComposedWord) { mWordComposer.reset(); if (alsoResetLastComposedWord) @@ -1242,11 +1246,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar KeyboardActionListener.SUGGESTION_STRIP_COORDINATE); } - private static int getEditorActionId(EditorInfo editorInfo) { - if (editorInfo == null) return 0; - return (editorInfo.actionLabel != null) - ? editorInfo.actionId - : (editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); + private static int getActionId(Keyboard keyboard) { + return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE; } private void performeEditorAction(int actionId) { @@ -1312,18 +1313,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSubtypeSwitcher.switchToShortcutIME(); break; case Keyboard.CODE_ACTION_ENTER: - performeEditorAction(getEditorActionId(getCurrentInputEditorInfo())); + performeEditorAction(getActionId(switcher.getKeyboard())); + break; + case Keyboard.CODE_ACTION_NEXT: + performeEditorAction(EditorInfo.IME_ACTION_NEXT); break; - case Keyboard.CODE_TAB: - handleTab(); - // There are two cases for tab. Either we send a "next" event, that may change the - // focus but will never move the cursor. Or, we send a real tab keycode, which some - // applications may accept or ignore, and we don't know whether this will move the - // cursor or not. So actually, we don't really know. - // So to go with the safer option, we'd rather behave as if the user moved the - // cursor when they didn't than the opposite. We also expect that most applications - // will actually use tab only for focus movement. - // To sum it up: do not update mExpectingUpdateSelection here. + case Keyboard.CODE_ACTION_PREVIOUS: + EditorInfoCompatUtils.performEditorActionPrevious(getCurrentInputConnection()); break; default: mSpaceState = SPACE_STATE_NONE; @@ -1490,30 +1486,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } - // TODO: Implement next and previous actions using other key code than tab's code. - private void handleTab() { - final int imeOptions = getCurrentInputEditorInfo().imeOptions; - if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) - && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) { - // TODO: This should be {@link #sendKeyCodePoint(int)}. - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); - return; - } - - final InputConnection ic = getCurrentInputConnection(); - if (ic == null) - return; - - final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - // True if keyboard is in either shift chording or manual shifted state. - final boolean isManualShifted = (keyboard != null && keyboard.isManualShifted()); - if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) && !isManualShifted) { - EditorInfoCompatUtils.performEditorActionNext(ic); - } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions) && isManualShifted) { - EditorInfoCompatUtils.performEditorActionPrevious(ic); - } - } - // ic may be null private boolean maybeStripSpaceWhileInBatchEdit(final InputConnection ic, final int code, final int spaceState, final boolean isFromSuggestionStrip) { @@ -1523,10 +1495,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } else if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState) && isFromSuggestionStrip) { - if (mSettingsValues.isMagicSpaceSwapper(code)) { + if (mSettingsValues.isWeakSpaceSwapper(code)) { return true; } else { - if (mSettingsValues.isMagicSpaceStripper(code)) { + if (mSettingsValues.isWeakSpaceStripper(code)) { removeTrailingSpaceWhileInBatchEdit(ic); } return false; @@ -1599,6 +1571,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar swapSwapperAndSpaceWhileInBatchEdit(ic); mSpaceState = SPACE_STATE_WEAK; } + // Some characters are not word separators, yet they don't start a new + // composing span. For these, we haven't changed the suggestion strip, and + // if the "add to dictionary" hint is shown, we should do so now. Examples of + // such characters include single quote, dollar, and others; the exact list is + // the list of characters for which we enter handleCharacterWhileInBatchEdit + // that don't match the test if ((isAlphabet...)) at the top of this method. + if (null != mSuggestionsView && mSuggestionsView.dismissAddToDictionaryHint()) { + mHandler.postUpdateBigramPredictions(); + } } Utils.Stats.onNonSeparator((char)primaryCode, x, y); } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 7a43cb827..d123b608f 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -35,8 +35,8 @@ public class SettingsValues { // From resources: public final int mDelayUpdateOldSuggestions; - public final String mMagicSpaceStrippers; - public final String mMagicSpaceSwappers; + public final String mWeakSpaceStrippers; + public final String mWeakSpaceSwappers; private final String mSuggestPuncs; public final SuggestedWords mSuggestPuncList; public final SuggestedWords mSuggestPuncOutputTextList; @@ -89,14 +89,14 @@ public class SettingsValues { // Get the resources mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); - mMagicSpaceStrippers = res.getString(R.string.magic_space_stripping_symbols); - mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols); + mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols); + mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols); if (LatinImeLogger.sDBG) { - final int length = mMagicSpaceStrippers.length(); - for (int i = 0; i < length; i = mMagicSpaceStrippers.offsetByCodePoints(i, 1)) { - if (isMagicSpaceSwapper(mMagicSpaceStrippers.codePointAt(i))) { - throw new RuntimeException("Char code " + mMagicSpaceStrippers.codePointAt(i) - + " is both a magic space swapper and stripper."); + final int length = mWeakSpaceStrippers.length(); + for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) { + if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) { + throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i) + + " is both a weak space swapper and stripper."); } } } @@ -107,7 +107,7 @@ public class SettingsValues { mSuggestPuncOutputTextList = createSuggestPuncOutputTextList(suggestPuncsSpec); mSymbolsExcludedFromWordSeparators = res.getString(R.string.symbols_excluded_from_word_separators); - mWordSeparators = createWordSeparators(mMagicSpaceStrippers, mMagicSpaceSwappers, + mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers, mSymbolsExcludedFromWordSeparators, res); mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); @@ -188,11 +188,11 @@ public class SettingsValues { return builder.setIsPunctuationSuggestions().build(); } - private static String createWordSeparators(final String magicSpaceStrippers, - final String magicSpaceSwappers, final String symbolsExcludedFromWordSeparators, + private static String createWordSeparators(final String weakSpaceStrippers, + final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators, final Resources res) { - String wordSeparators = magicSpaceStrippers + magicSpaceSwappers - + res.getString(R.string.magic_space_promoting_symbols); + String wordSeparators = weakSpaceStrippers + weakSpaceSwappers + + res.getString(R.string.weak_space_promoting_symbols); for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) { wordSeparators = wordSeparators.replace( symbolsExcludedFromWordSeparators.substring(i, i + 1), ""); @@ -215,14 +215,14 @@ public class SettingsValues { return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code)); } - public boolean isMagicSpaceStripper(int code) { + public boolean isWeakSpaceStripper(int code) { // TODO: this does not work if the code does not fit in a char - return mMagicSpaceStrippers.contains(String.valueOf((char)code)); + return mWeakSpaceStrippers.contains(String.valueOf((char)code)); } - public boolean isMagicSpaceSwapper(int code) { + public boolean isWeakSpaceSwapper(int code) { // TODO: this does not work if the code does not fit in a char - return mMagicSpaceSwappers.contains(String.valueOf((char)code)); + return mWeakSpaceSwappers.contains(String.valueOf((char)code)); } private static boolean isAutoCorrectEnabled(final Resources resources, diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java index d747a024c..e14c71cb5 100644 --- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java +++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java @@ -27,8 +27,7 @@ public class XmlParseUtils { @SuppressWarnings("serial") public static class ParseException extends XmlPullParserException { public ParseException(String msg, XmlPullParser parser) { - super(msg + " at line " + parser.getLineNumber() - + ", column " + parser.getColumnNumber()); + super(msg + " at " + parser.getPositionDescription()); } } |