All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.daisy.pipeline.braille.common.BrailleCode Maven / Gradle / Ivy

The newest version!
package org.daisy.pipeline.braille.common;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import org.daisy.pipeline.braille.common.util.Strings;

/**
 * Identification of a braille system, including details on contraction levels, specialized braille
 * systems, and additional elements relevant to braille production.
 *
 * For more info see the eBraille Braille Codes
 * Registry.
 */
public class BrailleCode {

	/**
	 * The level of contraction or complexity in the braille code
	 */
	public enum Grade {
		GRADE0,
		GRADE1,
		GRADE1_2, // not in spec!
		GRADE1_3, // not in spec!
		GRADE1_4, // not in spec!
		GRADE1_5, // not in spec!
		GRADE2,
		GRADE3,   // not in spec!
		GRADE4,   // not in spec!
		/** code does not use contractions or a grading system */
		NO_GRADE;

		@Override
		public String toString() {
			return name().toLowerCase().replaceAll("(?<=[0-9])_(?=[0-9])", ".")
			                           .replace("_", "-");
		}
	}

	/**
	 * Specialized systems
	 */
	public enum Specialization {
		/** 8-dot computer braille */
		COMP8,
		/** 6-dot computer braille */
		COMP6,
		/** code is only concerned with mathematics */
		MATH,
		/** code is only concerned with music */
		MUSIC,
		/** code is only concerned with phonetics */
		PHONETIC;

		@Override
		public String toString() {
			return name().toLowerCase();
		}
	}

	public String getSystem() {
		return system;
	}

	public Grade getGrade() {
		return grade;
	}

	public Optional getSpecialization() {
		return specialization;
	}

	@Override
	public String toString() {
		return id;
	}

	@Override
	public boolean equals(Object other) {
		if (!(other instanceof BrailleCode))
			return false;
		BrailleCode that = (BrailleCode)other;
		if (!that.id.equals(this.id))
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		return id.hashCode();
	}

	/**
	 * Return a IETF BCP 47 language tag with the braille code identifier integrated as a "t"
	 * (transformed content) extension (RFC 6497) with a "x0" (private use transform) part.
	 *
	 * @param sourceLanguage The language tag of the source (untransformed) content.
	 * @return the language tag of the braille content, which contains the "Brai" script subtag and
	 *         the braille code as a "t" extention.
	 */
	public Locale toLanguageTag(Locale sourceLanguage) {
		Locale.Builder b = new Locale.Builder();
		b.setLocale(sourceLanguage);
		b.setScript("Brai");
		b.setExtension('t', sourceLanguage.toLanguageTag().toLowerCase() + "-x0-" + idForLanguageTag);
		BrailleCode prev = idForLanguageTagCache.put(idForLanguageTag, this);
		if (prev != null && !prev.equals(this))
			throw new IllegalStateException(); // collision
		return b.build();
	}

	/**
	 * Get braille code from a IETF BCP 47 language tag. The language tag is expected to contain a
	 * "t" extension (RFC 6497) with a "x0" (private use transform) part, and the subtags after the
	 * "x0" subtag are expected to form the identifier of the braille code.
	 */
	public static BrailleCode fromLanguageTag(Locale language) throws IllegalArgumentException {
		String t = language.getExtension('t');
		if (t == null)
			throw new IllegalArgumentException("Language tag does not contain 't' extension: " + language);
		int i = t.indexOf("-x0-");
		if (i < 0)
			throw new IllegalArgumentException("Language tag does not contain 't' extension with 'x0' part: " + language);
		t = t.substring(i + 4);
		// HACK!
		// For now, because the BrailleCode feature is only used in the context of
		// dtbook-to-ebraille to pass braille code information from LiblouisTranslatorImpl through
		// language tags, and these language tags do not end up in the final eBraille publication,
		// we can put whatever we want in them. To simplify things, we generate the language tag
		// using a simple but lossy algorithm. For this reason, we don't try to parse it again, but
		// rather cache it when it is returned from toLanguageTag() (and hope there are no
		// collisions).
		BrailleCode cached = idForLanguageTagCache.get(t);
		if (cached == null)
			throw new IllegalArgumentException(); // should not happen if tag came from toLanguageTag()
		return cached;
	}

	public static BrailleCode parse(String identifier) throws IllegalArgumentException {
		List parts = new ArrayList<>();
		for (String part : identifier.split(" "))
			if (part.length() == 0)
				throw new IllegalArgumentException();
			else
				parts.add(part);
		if (parts.size() < 2)
			throw new IllegalArgumentException("Missing grade");
		String lastPart = parts.get(parts.size() - 1);
		parts.remove(parts.size() - 1);
		Grade grade;
		Specialization specialization = null; {
			try {
				grade = Grade.valueOf(lastPart.replaceAll("[-.]", "_").toUpperCase());
			} catch (IllegalArgumentException e) {
				if (parts.size() < 2)
					throw new IllegalArgumentException("Could not parse grade: " + lastPart);
				String secondToLastPart = parts.get(parts.size() - 1);
				parts.remove(parts.size() - 1);
				try {
					grade = Grade.valueOf(secondToLastPart.replaceAll("[-.]", "_").toUpperCase());
				} catch (IllegalArgumentException ee) {
					throw new IllegalArgumentException("Could not parse grade: " + secondToLastPart + " or " + lastPart);
				}
				try {
					specialization = Specialization.valueOf(lastPart.toUpperCase());
				} catch (IllegalArgumentException ee) {
					throw new IllegalArgumentException("Could not parse specialization: " + lastPart);
				}
			}
		}
		return new BrailleCode(Strings.join(parts, " "), grade, specialization);
	}

	private final String id;
	private final String system;
	private final Grade grade;
	private final Optional specialization;
	private final String idForLanguageTag; // for inclusion in language tag, see https://github.com/daisy/ebraille/issues/291
	private static final Map idForLanguageTagCache = new HashMap<>();

	/**
	 * Note that the {@code system} parameter, as well as the combination with {@code grade} and
	 * {@code specialization}, is currently not validated, as there are no registered braille codes
	 * at this point.
	 */
	public BrailleCode(String system, Grade grade, Specialization specialization) {
		if (system == null || grade == null)
			throw new IllegalArgumentException();
		this.system = system;
		this.grade = grade;
		this.specialization  = Optional.ofNullable(specialization);
		this.id = system + " "
			+ grade
			+ (specialization == null ? "" : (" " + specialization));
		List languageSubtags = new ArrayList<>(); {
			for (String part : system.split(" ")) {
				part = part.toLowerCase();
				part = part.replaceAll("[^a-z0-9]", "");
				if (part.length() < 3) // too short to make into a language subtag
					throw new IllegalArgumentException();
				if (part.length() > 8) // too long to make into a language subtag
					part = part.substring(0, 8);
				languageSubtags.add(part);
			}
			languageSubtags.add(grade.toString().replace(".", "")   // grade1.5 -->  grade15
			                                    .replace("-", "")); // no-grade --> nograde
			if (specialization != null)
				languageSubtags.add(specialization.toString());
		}
		this.idForLanguageTag = Strings.join(languageSubtags, "-");
	}
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy