/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.novelang.rendering.xslt.color; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.Charset; import java.util.Iterator; import java.util.List; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import static org.novelang.rendering.xslt.color.WebColors.State.*; /** * @author Laurent Caillette */ public class WebColors { private final List< ColorPair > colorPairs ; /*package*/ WebColors( final URL resourceUrl ) throws XMLStreamException, IOException { colorPairs = readColorPairs( resourceUrl ) ; } /*package*/ WebColors( final String xml ) throws XMLStreamException, IOException { colorPairs = readColorPairs( new ByteArrayInputStream( xml.getBytes( CHARSET ) ) ) ; } /** * Returns an {@code Iterable} returning {@code Iterator}s that cycle forever. * @return a non-null object returning a non-null {@code Iterator}. */ public Iterable< ColorPair > createColorCycler() { return new Iterable< ColorPair >() { @Override public Iterator< ColorPair > iterator() { return Iterators.cycle( colorPairs ) ; } } ; } public List< ColorPair > getColorPairs() { return colorPairs ; } // ========= // Singleton // ========= public static final WebColors INSTANCE ; /** * Still in {@code javascript} directory because the editor relies on JavaScript. */ @SuppressWarnings( { "HardcodedFileSeparator" } ) private static final String COLORS_DEFINITION = "/style/javascript/colors.htm" ; static { try { INSTANCE = new WebColors( ColorPair.class.getResource( COLORS_DEFINITION ) ); } catch( XMLStreamException e ) { throw new RuntimeException( e ) ; } catch( IOException e ) { throw new RuntimeException( e ) ; } } // ===== // Xalan // ===== // ================ // Reading resource // ================ private static final Charset CHARSET = Charset.forName( "UTF-8" ) ; private List< ColorPair > readColorPairs( final URL resourceUrl ) throws IOException, XMLStreamException { final InputStream inputStream = resourceUrl.openStream() ; try { return readColorPairs( inputStream ); } finally { if( inputStream != null ) { inputStream.close(); } } } /*package*/ List< ColorPair > readColorPairs( final InputStream inputStream ) throws IOException, XMLStreamException { final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance() ; String backgroundColorName = null ; String foregroundColorName = null ; final ImmutableList.Builder< ColorPair > colorPairsBuilder = new ImmutableList.Builder< ColorPair >() ; final XMLStreamReader parser = xmlInputFactory.createXMLStreamReader( inputStream ) ; for (int event = parser.next() ; event != XMLStreamConstants.END_DOCUMENT ; event = parser.next() ) { final QName elementQualifiedName ; switch( event ) { case XMLStreamConstants.START_ELEMENT : elementQualifiedName = parser.getName() ; switch( state ) { case NONE : changeState( DOCUMENT, NONE ) ; break ; case DOCUMENT : if( changeIfNameMatches( elementQualifiedName, HTML ) ) break ; case HTML : if( changeIfNameMatches( elementQualifiedName, BODY ) ) break ; case BODY: if( changeIfNameMatches( elementQualifiedName, DL ) ) break ; case DL: if( changeIfNameMatches( elementQualifiedName, DT ) ) break ; case DT: if( changeIfNameMatches( elementQualifiedName, STRONG ) ) break ; if( changeIfNameMatches( elementQualifiedName, EM ) ) break ; default : break ; } break ; case XMLStreamConstants.END_ELEMENT : elementQualifiedName = parser.getName() ; switch( state ) { case HTML : if( changeIfNameMatches( elementQualifiedName, DOCUMENT ) ) break ; case BODY : if( changeIfNameMatches( elementQualifiedName, HTML ) ) break ; case DL : if( changeIfNameMatches( elementQualifiedName, BODY ) ) break ; case DT : if( changeIfNameMatches( elementQualifiedName, DT, DL ) ) { colorPairsBuilder.add( new ColorPair( backgroundColorName, foregroundColorName ) ) ; backgroundColorName = null ; foregroundColorName = null ; } break ; case STRONG: if( changeIfNameMatches( elementQualifiedName, STRONG, DT ) ) break ; case EM : if( changeIfNameMatches( elementQualifiedName, EM, DT ) ) break ; case DONE : break ; default : break ; } break ; case XMLStreamConstants.CHARACTERS : // Color names short enough to read all at once. switch( state ) { case STRONG: backgroundColorName = parser.getText() ; break ; case EM : foregroundColorName = parser.getText() ; break ; } break ; case XMLStreamConstants.END_DOCUMENT : changeState( DONE, DOCUMENT ) ; break ; default : break ; } } return colorPairsBuilder.build() ; } // ============ // Parser state // ============ /*package visibility for static import*/ enum State { NONE( null ), DOCUMENT( null ), HTML( new QName( "html" ) ), BODY( new QName( "body" ) ), DL( new QName( "dl" ) ), DT( new QName( "dt" ) ), STRONG( new QName( "strong" ) ), EM( new QName( "em" ) ), DONE( null ); private final QName elementName ; State( final QName elementName ) { this.elementName = elementName ; } public QName getElementName() { return elementName ; } } private State state = NONE ; private boolean changeIfNameMatches( final QName elementName, final State newStateIfMatch ) { return changeIfNameMatches( elementName, newStateIfMatch, newStateIfMatch ) ; } private boolean changeIfNameMatches( final QName elementName, final State stateToMatch, final State newStateIfMatch ) { if( stateToMatch.getElementName().equals( elementName ) ) { state = newStateIfMatch ; return true ; } return false ; } private void changeState( final State newState, final State expectedCurrentState ) { if( state != expectedCurrentState ) { throw new IllegalStateException( "From state " + state + " tried to change to " + newState + " while in " + expectedCurrentState + "." ) ; } state = newState ; } }