com.adobe.xmp.core.XMPLanguageAlternative Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
// =================================================================================================
// 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