script-astra/Android/Sdk/sources/android-35/android/icu/impl/personname/PersonNamePattern.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

396 lines
15 KiB
Java

/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.personname;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import android.icu.text.PersonName;
/**
* A single name formatting pattern, corresponding to a single namePattern element in CLDR.
*/
class PersonNamePattern {
private String patternText; // for debugging
private Element[] patternElements;
public static PersonNamePattern[] makePatterns(String[] patternText, PersonNameFormatterImpl formatterImpl) {
PersonNamePattern[] result = new PersonNamePattern[patternText.length];
for (int i = 0; i < patternText.length; i++) {
result[i] = new PersonNamePattern(patternText[i], formatterImpl);
}
return result;
}
@Override
public String toString() {
return patternText;
}
private PersonNamePattern(String patternText, PersonNameFormatterImpl formatterImpl) {
this.patternText = patternText;
List<Element> elements = new ArrayList<>();
boolean inField = false;
boolean inEscape = false;
StringBuilder workingString = new StringBuilder();
for (int i = 0; i < patternText.length(); i++) {
char c = patternText.charAt(i);
if (inEscape) {
workingString.append(c);
inEscape = false;
} else {
switch (c) {
case '\\':
inEscape = true;
break;
case '{':
if (!inField) {
if (workingString.length() > 0) {
elements.add(new LiteralText(workingString.toString()));
workingString = new StringBuilder();
}
inField = true;
} else {
throw new IllegalArgumentException("Nested braces are not allowed in name patterns");
}
break;
case '}':
if (inField) {
if (workingString.length() > 0) {
elements.add(new NameFieldImpl(workingString.toString(), formatterImpl));
workingString = new StringBuilder();
} else {
throw new IllegalArgumentException("No field name inside braces");
}
inField = false;
} else {
throw new IllegalArgumentException("Unmatched closing brace in literal text");
}
break;
default:
workingString.append(c);
}
}
}
if (workingString.length() > 0) {
elements.add(new LiteralText(workingString.toString()));
}
this.patternElements = elements.toArray(new Element[0]);
}
public String format(PersonName name) {
StringBuilder result = new StringBuilder();
boolean seenLeadingField = false;
boolean seenEmptyLeadingField = false;
boolean seenEmptyField = false;
StringBuilder textBefore = new StringBuilder();
StringBuilder textAfter = new StringBuilder();
// if the name doesn't have a surname field and the pattern doesn't have a given-name field,
// we actually format a modified version of the name object where the contents of the
// given-name field has been copied into the surname field
name = hackNameForEmptyFields(name);
// the logic below attempts to implement the following algorithm:
// - If one or more fields at the beginning of the name are empty, also skip all literal text
// from the beginning of the name up to the first populated field.
// - If one or more fields at the end of the name are empty, also skip all literal text from
// the last populated field to the end of the name.
// - If one or more contiguous fields in the middle of the name are empty, skip the literal text
// between them, omit characters from the literal text on either side of the empty fields up to
// the first space on either side, and make sure that the resulting literal text doesn't end up
// with two spaces in a row.
for (Element element : patternElements) {
if (element.isLiteral()) {
if (seenEmptyLeadingField) {
// do nothing; throw away the literal text
} else if (seenEmptyField) {
textAfter.append(element.format(name));
} else {
textBefore.append(element.format(name));
}
} else {
String fieldText = element.format(name);
if (fieldText == null || fieldText.isEmpty()) {
if (!seenLeadingField) {
seenEmptyLeadingField = true;
textBefore.setLength(0);
} else {
seenEmptyField = true;
textAfter.setLength(0);
}
} else {
seenLeadingField = true;
seenEmptyLeadingField = false;
if (seenEmptyField) {
result.append(coalesce(textBefore, textAfter));
result.append(fieldText);
seenEmptyField = false;
} else {
result.append(textBefore);
textBefore.setLength(0);
result.append(element.format(name));
}
}
}
}
if (!seenEmptyField) {
result.append(textBefore);
}
return result.toString();
}
public int numPopulatedFields(PersonName name) {
int result = 0;
for (Element element : patternElements) {
result += element.isPopulated(name) ? 1 : 0;
}
return result;
}
public int numEmptyFields(PersonName name) {
int result = 0;
for (Element element : patternElements) {
result += (!element.isLiteral() && !element.isPopulated(name)) ? 1 : 0;
}
return result;
}
/**
* Stitches together the literal text on either side of an omitted field by deleting any
* non-whitespace characters immediately neighboring the omitted field and coalescing any
* adjacent spaces at the join point down to one.
* @param s1 The literal text before the omitted field.
* @param s2 The literal text after the omitted field.
*/
private String coalesce(StringBuilder s1, StringBuilder s2) {
// if the contents of s2 occur at the end of s1, we just use s1
if (endsWith(s1, s2)) {
s2.setLength(0);
}
// get the range of non-whitespace characters at the beginning of s1
int p1 = 0;
while (p1 < s1.length() && !Character.isWhitespace(s1.charAt(p1))) {
++p1;
}
// get the range of non-whitespace characters at the end of s2
int p2 = s2.length() - 1;
while (p2 >= 0 && !Character.isWhitespace(s2.charAt(p2))) {
--p2;
}
// also include one whitespace character from s1 or, if there aren't
// any, one whitespace character from s2
if (p1 < s1.length()) {
++p1;
} else if (p2 >= 0) {
--p2;
}
// concatenate those two ranges to get the coalesced literal text
String result = s1.substring(0, p1) + s2.substring(p2 + 1);
// clear out s1 and s2 (done here to improve readability in format() above))
s1.setLength(0);
s2.setLength(0);
return result;
}
/**
* Returns true if s1 ends with s2.
*/
private boolean endsWith(StringBuilder s1, StringBuilder s2) {
int p1 = s1.length() - 1;
int p2 = s2.length() - 1;
while (p1 >= 0 && p2 >= 0 && s1.charAt(p1) == s2.charAt(p2)) {
--p1;
--p2;
}
return p2 < 0;
}
private PersonName hackNameForEmptyFields(PersonName originalName) {
// this is a hack to deal with mononyms (name objects that don't have both a given name and a surname)--
// if the name object has a given-name field but not a surname field and the pattern either doesn't
// have a given-name field or only has "{given-initial}", we return a PersonName object that will
// return the value of the given-name field when asked for the value of the surname field and that
// will return null when asked for the value of the given-name field (all other field values and
// properties of the underlying object are returned unchanged)
PersonName result = originalName;
if (originalName.getFieldValue(PersonName.NameField.SURNAME, Collections.emptySet()) == null) {
boolean patternHasNonInitialGivenName = false;
for (PersonNamePattern.Element element : patternElements) {
if (!element.isLiteral()
&& ((NameFieldImpl)element).fieldID == PersonName.NameField.GIVEN
&& !((NameFieldImpl)element).modifiers.containsKey(PersonName.FieldModifier.INITIAL)) {
patternHasNonInitialGivenName = true;
break;
}
}
if (!patternHasNonInitialGivenName) {
return new GivenToSurnamePersonName(originalName);
}
}
return result;
}
/**
* A single element in a NamePattern. This is either a name field or a range of literal text.
*/
private interface Element {
boolean isLiteral();
String format(PersonName name);
boolean isPopulated(PersonName name);
}
/**
* Literal text from a name pattern.
*/
private static class LiteralText implements Element {
private String text;
public LiteralText(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
public boolean isLiteral() {
return true;
}
public String format(PersonName name) {
return text;
}
public boolean isPopulated(PersonName name) {
return false;
}
}
/**
* An actual name field in a NamePattern (i.e., the stuff represented in the pattern by text
* in braces). This class actually handles fetching the value for the field out of a
* PersonName object and applying any modifiers to it.
*/
private static class NameFieldImpl implements Element {
private PersonName.NameField fieldID;
private Map<PersonName.FieldModifier, FieldModifierImpl> modifiers;
public NameFieldImpl(String fieldNameAndModifiers, PersonNameFormatterImpl formatterImpl) {
List<PersonName.FieldModifier> modifierIDs = new ArrayList<>();
StringTokenizer tok = new StringTokenizer(fieldNameAndModifiers, "-");
this.fieldID = PersonName.NameField.forString(tok.nextToken());
while (tok.hasMoreTokens()) {
modifierIDs.add(PersonName.FieldModifier.forString(tok.nextToken()));
}
if (this.fieldID == PersonName.NameField.SURNAME && formatterImpl.shouldCapitalizeSurname()) {
modifierIDs.add(PersonName.FieldModifier.ALL_CAPS);
}
this.modifiers = new HashMap<>();
for (PersonName.FieldModifier modifierID : modifierIDs) {
this.modifiers.put(modifierID, FieldModifierImpl.forName(modifierID, formatterImpl));
}
if (this.modifiers.containsKey(PersonName.FieldModifier.RETAIN)
&& this.modifiers.containsKey(PersonName.FieldModifier.INITIAL)) {
FieldModifierImpl.InitialModifier initialModifier
= (FieldModifierImpl.InitialModifier) this.modifiers.get(PersonName.FieldModifier.INITIAL);
initialModifier.setRetainPunctuation(true);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append(fieldID);
for (PersonName.FieldModifier modifier : modifiers.keySet()) {
sb.append("-");
sb.append(modifier.toString());
}
sb.append("}");
return sb.toString();
}
public boolean isLiteral() {
return false;
}
public String format(PersonName name) {
Set<PersonName.FieldModifier> modifierIDs = new HashSet<>(modifiers.keySet());
String result = name.getFieldValue(fieldID, modifierIDs);
if (result != null) {
for (PersonName.FieldModifier modifierID : modifierIDs) {
result = modifiers.get(modifierID).modifyField(result);
}
}
return result;
}
public boolean isPopulated(PersonName name) {
String result = this.format(name);
return result != null && ! result.isEmpty();
}
}
/**
* Internal class used when formatting a mononym (a PersonName object that only has
* a given-name field). If the name doesn't have a surname field and the pattern
* doesn't have a given-name field (or only has one that produces an initial), we
* use this class to behave as though the value supplied in the given-name field
* had instead been supplied in the surname field.
*/
private static class GivenToSurnamePersonName implements PersonName {
private PersonName underlyingPersonName;
public GivenToSurnamePersonName(PersonName underlyingPersonName) {
this.underlyingPersonName = underlyingPersonName;
}
@Override
public String toString() {
return "Inverted version of " + underlyingPersonName.toString();
}
@Override
public Locale getNameLocale() {
return underlyingPersonName.getNameLocale();
}
@Override
public PreferredOrder getPreferredOrder() {
return underlyingPersonName.getPreferredOrder();
}
@Override
public String getFieldValue(NameField identifier, Set<FieldModifier> modifiers) {
if (identifier == NameField.SURNAME) {
return underlyingPersonName.getFieldValue(NameField.GIVEN, modifiers);
} else if (identifier == NameField.GIVEN) {
return null;
} else {
return underlyingPersonName.getFieldValue(identifier, modifiers);
}
}
}
}