blob: d604a2ec51baab866b7a57c3fc31c7b1d6169484 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.
*/
package com.google.gwt.i18n.shared;
import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
/**
* Base class for {@link BidiFormatter} and {@link SafeHtmlBidiFormatter} that
* contains their common implementation.
*/
public abstract class BidiFormatterBase {
/**
* Abstract factory class for BidiFormatterBase.
* BidiFormatterBase subclasses will usually have a non-abstract inner Factory
* class subclassed from this one, and use a static member of that class in
* order to prevent the needless creation of objects. For example, see
* {@link BidiFormatter}.
*/
protected abstract static class Factory<T extends BidiFormatterBase> {
private T[] instances;
@SuppressWarnings("unchecked")
public Factory() {
instances = (T[]) new BidiFormatterBase[6];
}
public abstract T createInstance(Direction contextDir,
boolean alwaysSpan);
public T getInstance(Direction contextDir,
boolean alwaysSpan) {
int index = calculateIndex(contextDir, alwaysSpan);
T formatter = instances[index];
if (formatter == null) {
formatter = createInstance(contextDir, alwaysSpan);
instances[index] = formatter;
}
return formatter;
}
// Index should be in the range [0, 5].
private int calculateIndex(Direction contextDir, boolean alwaysSpan) {
int i = contextDir == Direction.LTR ? 0 : contextDir == Direction.RTL ? 1
: 2;
if (alwaysSpan) {
i += 3;
}
return i;
}
}
/**
* A container class for direction-related string constants, e.g. Unicode
* formatting characters.
*/
static final class Format {
/**
* "left" string constant.
*/
public static final String LEFT = "left";
/**
* Unicode "Left-To-Right Embedding" (LRE) character.
*/
public static final char LRE = '\u202A';
/**
* Unicode "Left-To-Right Mark" (LRM) character.
*/
public static final char LRM = '\u200E';
/**
* String representation of LRM.
*/
public static final String LRM_STRING = Character.toString(LRM);
/**
* Unicode "Pop Directional Formatting" (PDF) character.
*/
public static final char PDF = '\u202C';
/**
* "right" string constant.
*/
public static final String RIGHT = "right";
/**
* Unicode "Right-To-Left Embedding" (RLE) character.
*/
public static final char RLE = '\u202B';
/**
* Unicode "Right-To-Left Mark" (RLM) character.
*/
public static final char RLM = '\u200F';
/**
* String representation of RLM.
*/
public static final String RLM_STRING = Character.toString(RLM);
// Not instantiable.
private Format() {
}
}
private boolean alwaysSpan;
private Direction contextDir;
protected BidiFormatterBase(Direction contextDir, boolean alwaysSpan) {
this.contextDir = contextDir;
this.alwaysSpan = alwaysSpan;
}
/**
* Like {@link #estimateDirection(String, boolean)}, but assumes {@code
* isHtml} is false.
*
* @param str String whose direction is to be estimated
* @return {@code str}'s estimated overall direction
*/
public Direction estimateDirection(String str) {
return BidiUtils.get().estimateDirection(str);
}
/**
* Estimates the direction of a string using the best known general-purpose
* method, i.e. using relative word counts. Direction.DEFAULT return value
* indicates completely neutral input.
*
* @param str String whose direction is to be estimated
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @return {@code str}'s estimated overall direction
*/
public Direction estimateDirection(String str, boolean isHtml) {
return BidiUtils.get().estimateDirection(str, isHtml);
}
/**
* Returns whether the span structure added by the formatter should be stable,
* i.e., spans added even when the direction does not need to be declared.
*/
public boolean getAlwaysSpan() {
return alwaysSpan;
}
/**
* Returns the context direction.
*/
public Direction getContextDir() {
return contextDir;
}
/**
* Returns whether the context direction is RTL.
*/
public boolean isRtlContext() {
return contextDir == Direction.RTL;
}
/**
* @see BidiFormatter#dirAttr(String, boolean)
*
* @param str String whose direction is to be estimated
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @return "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR text
* in non-LTR context; else, the empty string.
*/
protected String dirAttrBase(String str, boolean isHtml) {
return knownDirAttrBase(BidiUtils.get().estimateDirection(str, isHtml));
}
/**
* @see BidiFormatter#endEdge
*/
protected String endEdgeBase() {
return contextDir == Direction.RTL ? Format.LEFT : Format.RIGHT;
}
/**
* @see BidiFormatter#knownDirAttr(HasDirection.Direction)
*
* @param dir Given direction
* @return "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR text
* in non-LTR context; else, the empty string.
*/
protected String knownDirAttrBase(Direction dir) {
if (dir != contextDir) {
return dir == Direction.LTR ? "dir=ltr" : dir == Direction.RTL
? "dir=rtl" : "";
}
return "";
}
/**
* @see BidiFormatter#markAfter(String, boolean)
*
* @param str String after which the mark may need to appear
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
* else, the empty string.
*/
protected String markAfterBase(String str, boolean isHtml) {
str = BidiUtils.get().stripHtmlIfNeeded(str, isHtml);
return dirResetIfNeeded(str, BidiUtils.get().estimateDirection(str), false,
true);
}
/**
* @see BidiFormatter#mark()
*/
protected String markBase() {
return contextDir == Direction.LTR ? Format.LRM_STRING
: contextDir == Direction.RTL ? Format.RLM_STRING : "";
}
/**
* @see BidiFormatter#spanWrap(String, boolean, boolean)
*
* @param str The input string
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @param dirReset Whether to append a trailing unicode bidi mark matching the
* context direction, when needed, to prevent the possible garbling
* of whatever may follow {@code str}
* @return Input string after applying the above processing.
*/
protected String spanWrapBase(String str, boolean isHtml, boolean dirReset) {
Direction dir = BidiUtils.get().estimateDirection(str, isHtml);
return spanWrapWithKnownDirBase(dir, str, isHtml, dirReset);
}
/**
* @see BidiFormatter#spanWrapWithKnownDir(HasDirection.Direction, String, boolean, boolean)
*
* @param dir {@code str}'s direction
* @param str The input string
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @param dirReset Whether to append a trailing unicode bidi mark matching the
* context direction, when needed, to prevent the possible garbling
* of whatever may follow {@code str}
* @return Input string after applying the above processing.
*/
protected String spanWrapWithKnownDirBase(Direction dir, String str,
boolean isHtml, boolean dirReset) {
boolean dirCondition = dir != Direction.DEFAULT && dir != contextDir;
String origStr = str;
if (!isHtml) {
str = SafeHtmlUtils.htmlEscape(str);
}
StringBuilder result = new StringBuilder();
if (alwaysSpan || dirCondition) {
result.append("<span");
if (dirCondition) {
result.append(" ");
result.append(dir == Direction.RTL ? "dir=rtl" : "dir=ltr");
}
result.append(">" + str + "</span>");
} else {
result.append(str);
}
// origStr is passed (more efficient when isHtml is false).
result.append(dirResetIfNeeded(origStr, dir, isHtml, dirReset));
return result.toString();
}
/**
* @see BidiFormatter#startEdge
*/
protected String startEdgeBase() {
return contextDir == Direction.RTL ? Format.RIGHT : Format.LEFT;
}
/**
* @see BidiFormatter#unicodeWrap(String, boolean, boolean)
*
* @param str The input string
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @param dirReset Whether to append a trailing unicode bidi mark matching the
* context direction, when needed, to prevent the possible garbling
* of whatever may follow {@code str}
* @return Input string after applying the above processing.
*/
protected String unicodeWrapBase(String str, boolean isHtml,
boolean dirReset) {
Direction dir = BidiUtils.get().estimateDirection(str, isHtml);
return unicodeWrapWithKnownDirBase(dir, str, isHtml, dirReset);
}
/**
* @see BidiFormatter#unicodeWrapWithKnownDir(HasDirection.Direction, String, boolean, boolean)
*
* @param dir {@code str}'s direction
* @param str The input string
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @param dirReset Whether to append a trailing unicode bidi mark matching the
* context direction, when needed, to prevent the possible garbling
* of whatever may follow {@code str}
* @return Input string after applying the above processing.
*/
protected String unicodeWrapWithKnownDirBase(Direction dir, String str,
boolean isHtml, boolean dirReset) {
StringBuilder result = new StringBuilder();
if (dir != Direction.DEFAULT && dir != contextDir) {
result.append(dir == Direction.RTL ? Format.RLE : Format.LRE);
result.append(str);
result.append(Format.PDF);
} else {
result.append(str);
}
result.append(dirResetIfNeeded(str, dir, isHtml, dirReset));
return result.toString();
}
/**
* Returns a unicode BiDi mark matching the context direction (LRM or RLM) if
* {@code dirReset}, and if the overall direction or the exit direction of
* {@code str} are opposite to the context direction. Otherwise returns the
* empty string.
*
* @param str The input string
* @param dir {@code str}'s overall direction
* @param isHtml Whether {@code str} is HTML / HTML-escaped
* @param dirReset Whether to perform the reset
* @return A unicode BiDi mark or the empty string.
*/
private String dirResetIfNeeded(String str, Direction dir, boolean isHtml,
boolean dirReset) {
// endsWithRtl and endsWithLtr are called only if needed (short-circuit).
if (dirReset
&& ((contextDir == Direction.LTR &&
(dir == Direction.RTL ||
BidiUtils.get().endsWithRtl(str, isHtml))) ||
(contextDir == Direction.RTL &&
(dir == Direction.LTR ||
BidiUtils.get().endsWithLtr(str, isHtml))))) {
return contextDir == Direction.LTR ? Format.LRM_STRING
: Format.RLM_STRING;
} else {
return "";
}
}
}