| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 flash.localization; |
| |
| import org.xml.sax.helpers.DefaultHandler; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.ext.LexicalHandler; |
| |
| import javax.xml.parsers.SAXParserFactory; |
| import javax.xml.parsers.SAXParser; |
| import java.util.Map; |
| import java.util.Locale; |
| import java.util.Stack; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.Iterator; |
| import java.io.File; |
| import java.io.InputStream; |
| import java.io.BufferedInputStream; |
| import java.net.URL; |
| |
| /** |
| * ILocalizer implementation, which supports looking up text in XLR |
| * files.. |
| */ |
| public class XLRLocalizer implements ILocalizer |
| { |
| public XLRLocalizer() |
| { |
| // only resources |
| } |
| public XLRLocalizer( String path ) |
| { |
| findFiles( new File( path ), null ); |
| } |
| |
| public XLRTargetNode loadNode( Locale fileLocale, String fileId, Locale locale, String id ) |
| { |
| String key = getKey( fileLocale, fileId ); |
| XLRFile f = filedict.get( key ); |
| |
| if (f == null) |
| { |
| String resource = key.replaceAll( "\\.", "/" ) + ".xlr"; |
| URL url = getClass().getClassLoader().getResource( resource ); |
| |
| if (url != null) |
| { |
| f = new XLRFile( fileId, url ); |
| filedict.put( key, f ); |
| } |
| } |
| if (f != null) |
| { |
| f.load(); |
| XLRMessageNode messageNode = (XLRMessageNode) nodedict.get( id ); |
| if (messageNode != null) |
| { |
| XLRTargetNode targetNode = messageNode.getTarget( locale.toString() ); |
| return targetNode; |
| } |
| } |
| |
| return null; |
| } |
| |
| public XLRTargetNode checkPrefix( Locale fileLocale, String fileId, Locale locale, String id ) |
| { |
| XLRTargetNode t = loadNode( fileLocale, fileId, locale, id ); |
| if (t == null) |
| { |
| int sep = fileId.lastIndexOf( '$' ); |
| |
| if (sep == -1) |
| sep = fileId.lastIndexOf( '.' ); |
| |
| if (sep != -1) |
| t = checkPrefix( fileLocale, fileId.substring( 0, sep ), locale, id ); |
| } |
| return t; |
| } |
| |
| public XLRTargetNode checkLocales( Locale locale, String id ) |
| { |
| XLRTargetNode t = checkPrefix( locale, id, locale, id ); |
| |
| if ((t == null) && (locale.getCountry().length() > 0) && (locale.getVariant().length() > 0)) |
| t = checkPrefix( new Locale( locale.getLanguage(), locale.getCountry() ), id, locale, id ); |
| |
| if ((t == null) && (locale.getCountry().length() > 0)) |
| t = checkPrefix( new Locale( locale.getLanguage() ), id, locale, id ); |
| |
| if ((t == null)) |
| t = checkPrefix( null, id, locale, id ); |
| |
| return t; |
| } |
| |
| |
| public ILocalizedText getLocalizedText( Locale locale, String id ) |
| { |
| XLRMessageNode messageNode = (XLRMessageNode) nodedict.get( id ); |
| XLRTargetNode targetNode = null; |
| if (messageNode != null) |
| { |
| targetNode = messageNode.getTarget( locale.toString() ); |
| } |
| |
| if (targetNode == null) |
| { |
| targetNode = checkLocales( locale, id ); |
| } |
| |
| if (targetNode == null) |
| { |
| return null; |
| } |
| |
| return new XLRLocalizedText( targetNode ); |
| } |
| |
| private String getKey( Locale locale, String id ) |
| { |
| String key = id; |
| if (locale != null) |
| { |
| if (locale.getLanguage().length() > 0) |
| { |
| key += "_" + locale.getLanguage(); |
| if (locale.getCountry().length() > 0) |
| { |
| key += "_" + locale.getCountry(); |
| |
| if (locale.getVariant().length() > 0) |
| { |
| key += "_" + locale.getVariant(); |
| } |
| } |
| } |
| } |
| return key; |
| } |
| |
| private Map<String, XLRFile> filedict = new HashMap<String, XLRFile>(); |
| private Map<String, XLRNode> nodedict = new HashMap<String, XLRNode>(); |
| |
| private class XLRFile |
| { |
| public XLRFile( String prefix, URL url ) |
| { |
| this.prefix = prefix; |
| this.url = url; |
| } |
| |
| public void load() |
| { |
| if (loaded) |
| { |
| return; |
| } |
| try |
| { |
| InputStream in = new BufferedInputStream( this.url.openStream() ); |
| SAXParserFactory factory = SAXParserFactory.newInstance(); |
| factory.setNamespaceAware( false ); // FIXME |
| |
| XLRHandler xmlHandler = new XLRHandler( nodedict, prefix ); |
| CDATAHandler cdataHandler = new CDATAHandler( xmlHandler ); |
| |
| SAXParser parser = factory.newSAXParser(); |
| parser.setProperty("http://xml.org/sax/properties/lexical-handler", cdataHandler); |
| parser.parse( in, xmlHandler ); |
| } |
| catch (Exception e) |
| { |
| e.printStackTrace( ); |
| } |
| loaded = true; |
| } |
| |
| private boolean loaded = false; |
| private final URL url; |
| private final String prefix; |
| } |
| |
| private void findFiles( File f, String relative ) |
| { |
| try |
| { |
| if (!f.exists()) |
| return; |
| |
| if (f.isDirectory()) |
| { |
| File[] files = f.listFiles(); |
| |
| for (int i = 0; i < files.length; ++i) |
| { |
| findFiles( files[i].getAbsoluteFile(), ((relative == null)? "":(relative + ".")) + files[i].getName() ); |
| } |
| } |
| else |
| { |
| if (!f.getName().endsWith( ".xlr" )) |
| return; |
| else |
| { |
| String id = relative.substring( 0, relative.length() - ".xlr".length() ); |
| |
| String prefix = id; |
| int dot = id.lastIndexOf( '.' ); |
| int underscore = -1; |
| if (dot != -1) |
| { |
| underscore = id.indexOf( '_', dot ); |
| } |
| else |
| { |
| underscore = id.indexOf( '_' ); |
| } |
| if (underscore != -1) |
| { |
| prefix = id.substring( 0, underscore ); |
| } |
| |
| filedict.put( id, new XLRFile( prefix, f.toURL() ) ); |
| } |
| } |
| } |
| catch (Exception e) |
| { |
| e.printStackTrace( ); |
| } |
| } |
| |
| private class XLRLocalizedText implements ILocalizedText |
| { |
| public XLRLocalizedText( XLRTargetNode node ) |
| { |
| this.node = node; |
| } |
| public String format( Map parameters ) |
| { |
| StringBuilder buffer = new StringBuilder(); |
| String s = node.execute( buffer, node.locale, parameters )? buffer.toString() : null; |
| if (s != null) |
| { |
| s = LocalizationManager.replaceInlineReferences( s, parameters ); |
| } |
| return s; |
| } |
| private XLRTargetNode node; |
| } |
| |
| private abstract class XLRNode |
| { |
| public LinkedList<XLRNode> children = new LinkedList<XLRNode>(); |
| public boolean execute( StringBuilder buffer, String locale, Map parameters ) |
| { |
| boolean success = false; |
| for (Iterator<XLRNode> it = children.iterator(); it.hasNext(); ) |
| { |
| XLRNode child = it.next(); |
| |
| if (child.execute( buffer, locale, parameters )) |
| { |
| success = true; |
| } |
| } |
| return success; |
| } |
| } |
| |
| private class XLRChoiceNode extends XLRNode |
| { |
| public boolean execute( StringBuilder buffer, String locale, Map parameters ) |
| { |
| for (Iterator<XLRNode> it = children.iterator(); it.hasNext(); ) |
| { |
| XLRNode child = it.next(); |
| |
| if (child.execute( buffer, locale, parameters )) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private class XLRMessageNode extends XLRChoiceNode |
| { |
| public XLRMessageNode( String id ) |
| { |
| this.id = id; |
| } |
| public XLRTargetNode getTarget( String locale ) |
| { |
| for (Iterator<XLRNode> it = children.iterator(); it.hasNext();) |
| { |
| XLRNode node = it.next(); |
| |
| if ((node instanceof XLRTargetNode) && ((XLRTargetNode) node).matchesLocale( locale )) |
| { |
| return (XLRTargetNode) node; |
| } |
| } |
| return null; |
| } |
| public final String id; |
| } |
| |
| private class XLRTargetNode extends XLRNode |
| { |
| public XLRTargetNode( String locale ) |
| { |
| this.locale = locale; |
| } |
| public boolean matchesLocale( String locale ) |
| { |
| return (((this.locale == null) && (locale == null)) || locale.equalsIgnoreCase( this.locale )); |
| |
| } |
| public boolean execute( StringBuilder buffer, String locale, Map parameters ) |
| { |
| if (matchesLocale( locale )) |
| { |
| return super.execute( buffer, locale, parameters ); |
| } |
| return false; |
| } |
| public final String locale; |
| } |
| |
| private class XLRTextNode extends XLRNode |
| { |
| public XLRTextNode( String text ) |
| { |
| this.text = text; |
| } |
| public boolean execute( StringBuilder buffer, String locale, Map parameters ) |
| { |
| boolean success = false; |
| if (text != null) |
| { |
| success = true; |
| buffer.append( text ); |
| } |
| boolean result = super.execute( buffer, locale, parameters ); |
| return success || result; |
| } |
| public final String text; |
| } |
| |
| private class XLRVariableNode extends XLRNode |
| { |
| public XLRVariableNode( String name ) |
| { |
| this.varname = name; |
| } |
| public boolean execute( StringBuilder buffer, String locale, Map parameters ) |
| { |
| boolean success = false; |
| if (varname != null) |
| { |
| success = parameters.containsKey( varname ) && (parameters.get( varname ) != null); |
| if (success) |
| { |
| buffer.append( parameters.get( varname ).toString()); |
| } |
| } |
| success |= super.execute( buffer, locale, parameters ); |
| return success; |
| } |
| public String varname; |
| } |
| |
| private class XLRMatchNode extends XLRNode |
| { |
| public String varname; |
| public String text = null; |
| public String pattern = null; |
| public XLRMatchNode( String varname, String pattern ) |
| { |
| this.varname = varname; |
| this.pattern = pattern; |
| } |
| public boolean execute( StringBuilder buffer, String locale, Map parameters ) |
| { |
| String value = null; |
| |
| if ((varname != null) && parameters.containsKey( varname ) && parameters.get( varname ) != null) |
| { |
| value = parameters.get( varname ).toString(); |
| } |
| if (value == null) |
| { |
| value = ""; |
| } |
| // match based on the value being non-zero length, non-zero, or not "false" if pattern isn't set |
| |
| boolean matched = false; |
| if (pattern == null) |
| { |
| if ((value != null) && (value.length() > 0)) |
| { |
| matched = !(value.equalsIgnoreCase( "false" ) || value.equals( "0" )); |
| } |
| else |
| { |
| matched = false; // null string |
| } |
| } |
| else |
| { |
| // to match an empty string, try pattern of "^$" |
| matched = value.matches( pattern ); |
| } |
| |
| if (matched) |
| { |
| super.execute( buffer, locale, parameters ); |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| } |
| |
| private class XLRHandler extends DefaultHandler |
| { |
| public XLRHandler( Map<String, XLRNode> nodedict, String base ) |
| { |
| this.nodedict = nodedict; // id -> messagenode |
| this.base = base; |
| } |
| public Stack<XLRNode> context = new Stack<XLRNode>(); |
| private String fileLocale = null; |
| private String base = null; |
| private Map<String, XLRNode> nodedict; |
| StringBuilder textBuffer = new StringBuilder(128); |
| protected boolean inCDATA = false; |
| public void startElement (String uri, String localName, |
| String qName, Attributes attributes) |
| throws SAXException |
| { |
| XLRNode current = null; |
| if (context.size() > 0) |
| current = context.peek(); |
| |
| // common shortcuts... |
| String locale = attributes.getValue( "locale" ); |
| if (locale == null) |
| locale = fileLocale; |
| String text = attributes.getValue( "text" ); |
| |
| XLRNode node = null; |
| if ("messages".equals( qName )) |
| { |
| fileLocale = attributes.getValue( "locale" ); |
| if (attributes.getValue( "idbase" ) != null) |
| base = attributes.getValue( "idbase" ); |
| } |
| else if ("message".equals( qName )) |
| { |
| String id = attributes.getValue( "id" ); |
| |
| if (base != null) |
| id = base + "." + id; |
| |
| node = nodedict.get( id ); |
| if (node == null) |
| { |
| node = new XLRMessageNode( id ); |
| nodedict.put( id, node ); |
| } |
| if ((text != null) && (locale != null)) // check errors |
| { |
| XLRTargetNode targetNode = new XLRTargetNode( locale ); |
| node.children.add( targetNode ); |
| XLRTextNode textNode = new XLRTextNode( text ); |
| targetNode.children.add( textNode ); |
| } |
| |
| |
| context.push( node ); |
| } |
| else if ("target".equals( qName )) |
| { |
| node = new XLRTargetNode( locale ); |
| if (text != null) |
| node.children.add( new XLRTextNode( text )); |
| |
| current.children.add( node ); |
| context.push( node ); |
| } |
| else if ("text".equals( qName )) |
| { |
| String value = attributes.getValue( "value" ); |
| |
| node = new XLRTextNode( value ); |
| |
| current.children.add( node ); |
| context.push( node ); |
| } |
| else if ("variable".equals( qName )) |
| { |
| String name = attributes.getValue( "name" ); |
| |
| node = new XLRVariableNode( name ); |
| current.children.add( node ); |
| context.push( node ); |
| |
| } |
| else if ("match".equals( qName )) |
| { |
| node = new XLRMatchNode( attributes.getValue( "variable" ), attributes.getValue( "pattern" ) ); |
| if (text != null) |
| node.children.add( new XLRTextNode( text )); |
| |
| current.children.add( node ); |
| context.push( node ); |
| } |
| else if ("select".equals( qName )) |
| { |
| node = new XLRChoiceNode(); |
| current.children.add( node ); |
| context.push( node ); |
| } |
| else |
| { |
| throw new SAXParseException( "blorp", null ); // fixme |
| } |
| |
| } |
| |
| public void endElement (String uri, String localName, String qName) |
| throws SAXException |
| { |
| XLRNode current = null; |
| if (context.size() > 0) |
| current = context.pop(); |
| |
| if ("messages".equals( qName )) |
| { |
| // done |
| } |
| else if ("text".equals( qName )) |
| { |
| if (textBuffer.length() > 0) |
| { |
| current.children.add( new XLRTextNode( textBuffer.toString() ) ); |
| } |
| } |
| else if ("variable".equals( qName )) |
| { |
| if (textBuffer.length() > 0) |
| { |
| ((XLRVariableNode) current).varname = textBuffer.toString(); |
| } |
| } |
| textBuffer.setLength( 0 ); |
| } |
| public void characters (char ch[], int start, int length) |
| throws SAXException |
| { |
| if (inCDATA) |
| { |
| textBuffer.append(ch, start, length); |
| } |
| else |
| { |
| String s = new String( ch, start, length ).trim(); |
| |
| if (s.length() > 0) |
| textBuffer.append( s ); |
| } |
| |
| } |
| |
| |
| public void ignorableWhitespace (char ch[], int start, int length) |
| throws SAXException |
| { |
| // no op |
| } |
| public void warning ( SAXParseException e) |
| throws SAXException |
| { |
| // no op |
| } |
| |
| |
| public void error (SAXParseException e) |
| throws SAXException |
| { |
| // no op |
| } |
| |
| |
| public void fatalError (SAXParseException e) |
| throws SAXException |
| { |
| throw e; |
| } |
| |
| |
| |
| } |
| private class CDATAHandler implements LexicalHandler |
| { |
| private XLRHandler parentHandler; |
| public CDATAHandler( XLRHandler h ) |
| { |
| parentHandler = h; |
| } |
| public void startCDATA() throws SAXException |
| { |
| parentHandler.inCDATA = true; |
| } |
| |
| public void endCDATA() throws SAXException |
| { |
| parentHandler.inCDATA = false; |
| } |
| |
| public void startDTD(String s, String s1, String s2) throws SAXException |
| { |
| } |
| |
| public void endDTD() throws SAXException |
| { |
| } |
| |
| public void startEntity(String s) throws SAXException |
| { |
| } |
| |
| public void endEntity(String s) throws SAXException |
| { |
| } |
| |
| public void comment(char[] chars, int i, int i1) throws SAXException |
| { |
| } |
| } |
| |
| |
| } |