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

com.adobe.xmp.core.XMPLanguageAlternative Maven / Gradle / Ivy

// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2012 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
package com.adobe.xmp.core;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.adobe.xmp.core.impl.XMPConst;
import com.adobe.xmp.core.namespace.XML;

/**
 * Language alternatives allow the text value of a property to be chosen based on a desired
 * language. 
 * XMPLanguageAlternative is implemented as a {@link XMPArray} (Alternate) whose children are
 * {@link XMPSimple} with text value, each of which must have a language qualifier (xml:lang) 
 * associated with it.
 */
public class XMPLanguageAlternative
{

	XMPArray array = null;
	
	/**
	 * Gets underlying {@link XMPArray}
	 * @return XMPArray
	 */
	public XMPArray getArray()
	{
		return array;
	}
	
	/**
	 * Constructs {@link XMPLanguageAlternative} object from the given XMPArray.
	 * Callers of this method must ensure that the XMPArray object sent to this
	 * method should represent a Lang ALT as specified by XMP Specification.
	 * @param array
	 */
	private XMPLanguageAlternative( XMPArray array )
	{
	    this.array = array;
	}
	
	/**
	 * Constructs a {@link XMPLanguageAlternative} which wraps the given {@link XMPArray}. If 
	 * this {@link XMPArray} does not represent a Lang Alt as per XMP Specification this
	 * method returns null.
	 * To detect whether a language alternative, these prerequisites 
	 * must be fulfilled:
	 * 
    *
  • the array contains only simple properties
  • *
  • at least one property has an xml:lang qualifier.
  • *
* @param array */ public static XMPLanguageAlternative newInstance( XMPArray array ) { boolean foundLangAlt = false; // check form if ( array.getForm().equals(XMPArray.Form.ALTERNATIVE) ) { //check if all child elements are XMPSimple // and at least one property has an xml:lang qualifier Iterator it = array.iterator(); while (it.hasNext() ) { XMPNode node = it.next(); if ( ! (node instanceof XMPSimple) ) { return null; } else { if ( getLanguage(node.adaptTo(XMPSimple.class)) != null ) { foundLangAlt = true; } } } if ( !array.isEmpty() && !foundLangAlt ) { return null; } } else { return null; } // No errors detected. Create and return a XMPLanguageAlternative return new XMPLanguageAlternative(array); } /** * This will normalize the alternative array to meet the criteria of a XMPLanguage alternative. * This is only important if a XMPArray is constructed outside of this facade and then used within this context. * If you only use this facade, a currupt XMPArray will never be created. * * Normalization will do the following: *
    *
  • x-default is moved to top (first one if multiple)
  • *
  • Items with empty languages become the default language, when there is no "x-default". (first one if multiple)
  • *
  • Any other item qualifiers than xml:lang are deleted.
  • *
  • language will be normalized to RFC 3066 form *
* after cleanup, other x-defaults than the first one and other items with no xml:lang will not be deleted at this point * x-defaults will be deleted while setting a new default. Emptys will be preserved but not accessible with this fassade. */ public void normalize() { if (array.isEmpty()) { return; } XMPSimple oldDefault = array.getSimple(0); String oldXMLLang = null; int newDefault = -1; if ( oldDefault != null ) { oldXMLLang = getLanguage(oldDefault); for (int i = 0; i 0 ) { //other qual --> cleanup cleanupLangAltQualifier(langProperty.accessQualifiers()); } // do nothing, just keep entries with no xml:lang } else { if ( langProperty.accessQualifiers().size() > 1 ) { //other qual --> cleanup cleanupLangAltQualifier(langProperty.accessQualifiers()); } if (newDefault < 0 && XMPConst.X_DEFAULT.equals(xmlLang)) { // save first x-default position newDefault = i; } // normalize language if needed if needed String normLang = normalizeLangValue(xmlLang); if ( !normLang.equals(xmlLang) ) { langProperty.accessQualifiers().setSimple( XML.URI, XML.LANG , normLang ); } } } } // move x-default up if not if ( newDefault > 0 ) { XMPSimple newDefaultItem = array.getSimple(newDefault); XMPSimple newEntry = array.insertSimple(0, newDefaultItem.getValue()); newEntry.accessQualifiers().setSimple( XML.URI, XML.LANG , getLanguage(newDefaultItem)); //remove from old position array.remove(newDefault); } else { //set xml:lang for old x-default, if it has no (or empty) xml:lang if ( oldXMLLang == null || oldXMLLang.length() == 0 ) { oldDefault.accessQualifiers().setSimple( XML.URI, XML.LANG, XMPConst.X_DEFAULT ); } } } } private void cleanupLangAltQualifier( XMPQualifiers qualifiers) { ArrayList removeList = new ArrayList(); for (XMPNode item: qualifiers) { if (! (item instanceof XMPSimple) || !(item.getNamespace().equals(XML.URI) && item.getName().equals(XML.LANG)) ) { //add to remove list removeList.add(item); } } for (XMPNode toRemove:removeList) { qualifiers.remove(toRemove.getNamespace(), toRemove.getName()); } } /** * Checks if the Language Alternative is empty * @return True if the array is empty False otherwise */ public boolean isEmpty() { return array.isEmpty(); } /** * returns the number of elements * @return the number of elements */ public int size() { return array.size(); } /** * Delete all elements */ public void clear() { array.clear(); } private static String getLanguage ( XMPSimple simple ) { XMPSimple qual = simple.accessQualifiers().getSimple( XML.URI, XML.LANG ); if ( qual != null ) { return qual.getValue(); } return null; } /** * Set or overwrites a localized text entry for this language alternative. * @param language the language String, has to follow the RFC 3066 format * @param value the value for this language alternative entry * @return the localized text entry just created or null if nothing was created * @throws IllegalArgumentException IllegalArgumentException is thrown if value is null */ public XMPSimple setLocalizedText( String language, String value ) { if(value == null) { throw new IllegalArgumentException("Value should not be null while setting localized text."); } String lang = normalizeLangValue(language); // look for an existing item with the same locale, // replace it and return XMPSimple item = null; for ( int i = 0; i < array.size(); i++ ) { item = array.getSimple(i); if ( item != null ) { String xmlLang = getLanguage( item ); if ( lang != null && lang.equals(xmlLang) ) { //overwrite item.setValue( value ); return item; } } } // append a new language node; // in case of x-default, add it to the first position if ( XMPConst.X_DEFAULT.equals(lang) ) { // the case were old default is x-default and new default is x-default // it was already overwritten by the code above item = array.insertSimple(0, value); } else { item = array.appendSimple(value); } item.accessQualifiers().setSimple( XML.URI, XML.LANG, lang ); //remove old XDefault entries //array = removeDuplicatedXDefault(array); return item; } /** * remove duplicated x-default entries on positions other that the first one * @param array * @return */ private XMPArray removeDuplicatedXDefault( XMPArray array ) { //remove old XDefault entries if ( array.size() > 1 ) { for (int i=array.size()-1; i>0; i-- ) { XMPSimple current = array.getSimple(i); if (current != null) { String currentLang = getLanguage( current ); if ( currentLang.equals(XMPConst.X_DEFAULT) ) { array.remove(i); } } } } return array; } /** * Set or overwrites the default localized text entry for this language alternative. * @param language the language of the default entry (or null or x-default. if no specific language is given) * @param value the value for this language alternative entry * @return the localized text entry just created or null if nothing was created */ public XMPSimple setDefaultText( String language, String value ) { String lang = normalizeLangValue(language); if ( lang== null || lang.equals("") ) { lang = XMPConst.X_DEFAULT; } XMPSimple oldDefault = array.getSimple(0); XMPSimple newSimple = null; // if the first entry has language x-default, overwrite value if ( oldDefault != null ) { String oldDefaultLang = getLanguage( oldDefault ); if ( oldDefaultLang == null ) { oldDefault.accessQualifiers().setSimple( XML.URI, XML.LANG, XMPConst.X_DEFAULT ); oldDefaultLang = XMPConst.X_DEFAULT; } // overwrite old x-default value, do not insert if ( XMPConst.X_DEFAULT.equals( oldDefaultLang )) { oldDefault.setValue( value ); oldDefault.accessQualifiers().setSimple( XML.URI, XML.LANG, lang ); return oldDefault; } } // otherwise insert new one at beginning of the list newSimple = array.insertSimple(0, value); newSimple.accessQualifiers().setSimple( XML.URI, XML.LANG, lang ); //remove old XDefault entries array = removeDuplicatedXDefault(array); return newSimple; } /** * Sets the default text entry (first one in array) in an language alternative array. * If a new entry must be created (array is empty), x-default will be used as language * @param value the value for this language alternative entry * @return the localized text entry just created or null if nothing was created */ public XMPSimple setDefaultText( String value ) { if ( array.size() == 0 ) { //create a new x-default entry (with lang x-default) XMPSimple newSimple = array.insertSimple(0, value); newSimple.accessQualifiers().setSimple( XML.URI, XML.LANG, XMPConst.X_DEFAULT ); return newSimple; } else { return setDefaultText( XMPConst.X_DEFAULT, value ); } } /** * Sets the entry with a certain language as the default entry (pushes it to the first location) * @param language the language to set as default * @return the new default entry or null if nothing could be set (language entry does not exist) */ public XMPSimple setDefaultLanguage( String language ) { String lang = normalizeLangValue(language); if ( XMPConst.X_DEFAULT.equals(lang) ) { //nothing happens just return the default return getDefaultText(); } else { Iterator it = array.iterator(); XMPNode node = null; XMPSimple simp = null; int cnt = 0; String xmlLang = ""; boolean found = false; while ( it.hasNext() ) { node = it.next(); if (node instanceof XMPSimple ) { simp = node.adaptTo( XMPSimple.class ); xmlLang = getLanguage( simp ); if ( lang.equals(xmlLang) ) { found = true; break; } } cnt++; } if ( found ) { if ( cnt == 0 ) //first (default) entry should be set { return simp; } // delete old default, if it has no language (just "x-default" as lang) XMPSimple oldDefault = array.getSimple(0); if ( oldDefault != null ) { if ( XMPConst.X_DEFAULT.equals(getLanguage(oldDefault)) ) { array.remove(0); cnt--; } } // move to first position // 1.remove from old position array.remove( cnt ); // 2. create at front (with same XMPSimple newSimp = array.insertSimple(0, simp.getValue()); newSimp.accessQualifiers().setSimple( XML.URI, XML.LANG, xmlLang ); return newSimp; } else { return null; } } } /** * Returns the language alternative array item for a specific language * If language is x-default the first entry of the array is returned, even * if someone accidently added an "x-default" entry * at another position than the first one (not possible while using this class) * @param language the language for the desired array item * @return the array item requested or null if it does not exist */ public XMPSimple getLocalizedText( String language ) { String lang = normalizeLangValue(language); //return default if language is x-default if ( lang== null || lang.equals("") || XMPConst.X_DEFAULT.equals(lang) ) { return getDefaultText(); } else { Iterator it = array.iterator(); XMPSimple simp = null; XMPSimple firstPartialMatch = null; XMPNode node = null; String xmlLang = ""; while ( it.hasNext() ) { node = it.next(); if ( node instanceof XMPSimple ) //skip non Simple entries { simp = node.adaptTo( XMPSimple.class ); xmlLang = getLanguage( simp ); if ( xmlLang != null ) { // #1 first check for complete match of language a_b_c if ( lang.equals(xmlLang) ) { return simp; } // #2 now try partial match // loop removes more and more tags en-US-xyz --> en-US --> en // from current item language while ( xmlLang.length() > 0 ) { int pos = xmlLang.lastIndexOf("-"); if ( pos >= 0 ) { xmlLang = xmlLang.substring(0, pos); if ( firstPartialMatch == null && lang.equals(xmlLang) ) { // store first partial match and return this in case we have no exact match firstPartialMatch = simp; } } else { break; } } } } } return firstPartialMatch; // returns either a partial match or null if nothing was found } } /** * Returns the default language from an alternative array. * This is always the first entry (even if someone accidently added an "x-default" entry * at another position than the first one (not possible while using this class) * @return the array item requested or null if it does not exist */ public XMPSimple getDefaultText( ) { XMPSimple simp = array.getSimple(0); //special case, if the first entry is no simple property: find the next valid entry if (simp==null && array.size()>1) { for (int i=1; i getAvailableLanguages() { ArrayList languages = new ArrayList(); Iterator it = array.iterator(); XMPNode current = null; String xmlLang = ""; while ( it.hasNext() ) { current = it.next(); if (current instanceof XMPSimple ) // skip non simple entries { xmlLang = getLanguage(current.adaptTo(XMPSimple.class)); languages.add(xmlLang); } } return languages; } /** * Removes the specified language item from an alternative array * @param language the language that shall be removed * @return the removed item or null if it did not exist */ public XMPSimple removeLocalizedText( String language ) { // first check if language is "x-default" String lang = normalizeLangValue(language); if ( XMPConst.X_DEFAULT.equals(lang) ) { return removeDefaultText(); } else { Iterator it = array.iterator(); XMPNode current = null; String xmlLang = ""; int index = 0; while ( it.hasNext() ) { current = it.next(); if (current instanceof XMPSimple ) // skip non empty entries { xmlLang = getLanguage(current.adaptTo(XMPSimple.class)); if ( lang.equals(xmlLang) ) { current = array.remove(index); return current.adaptTo(XMPSimple.class); } } index++; } return null; } } /** * removes the default entry (the first one) and the second one will be default * @return the removed item or null if it did not exist */ public XMPSimple removeDefaultText() { if ( !array.isEmpty() ) { XMPSimple simple = array.getSimple(0); if (simple != null) { XMPNode removed = array.remove(0); assert ( removed instanceof XMPSimple ); // already checked in constructor return removed.adaptTo( XMPSimple.class ); } } // in case of an empty array or first entry is not a simple property return null; } /** * Normalize an xml:lang value so that comparisons are effectively case * insensitive as required by RFC 3066 (which superceeds RFC 1766). The * normalization rules: *
    *
  • The primary subtag is lower case, the suggested practice of ISO 639. *
  • All 2 letter secondary subtags are upper case, the suggested * practice of ISO 3166. *
  • All other subtags are lower case. *
* * @param value * raw value * @return Returns the normalized value. */ private String normalizeLangValue( String value ) { //normalize null to x-default if (value == null) { return XMPConst.X_DEFAULT; } // don't normalize x-default if (XMPConst.X_DEFAULT.equals(value)) { return value; } int subTag = 1; StringBuffer buffer = new StringBuffer(); for (int i = 0; i < value.length(); i++) { switch (value.charAt(i)) { case '-': case '_': // move to next subtag and convert underscore to hyphen buffer.append('-'); subTag++; break; case ' ': // remove spaces break; default: // convert second subtag to uppercase, all other to lowercase if (subTag != 2) { buffer.append(Character.toLowerCase(value.charAt(i))); } else { buffer.append(Character.toUpperCase(value.charAt(i))); } } } return buffer.toString(); } public Iterator iterator() { return array.iterator(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy