/* * 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.parser.xpath; import java.util.Iterator; import org.jaxen.DefaultNavigator; import org.jaxen.JaxenConstants; import org.jaxen.UnsupportedAxisException; import org.jaxen.XPath; import org.jaxen.saxpath.SAXPathException; import org.jaxen.util.SingleObjectIterator; import static org.novelang.parser.NodeKindTools.tokenNameAsXmlElementName; import org.novelang.common.SyntacticTree; import org.novelang.common.tree.Treepath; import org.novelang.common.tree.TreepathTools; import org.novelang.parser.NodeKind; /** * Jaxen-specific logic to apply XPath expressions to a {@link Treepath} of * {@link SyntacticTree}s. * <p> * TODO: add a set of {@link NodeKind}s recognized as elements. * <p> * TODO (not urgent): suppport namespaces in an orthodox manner * with {@link org.jaxen.XPath#addNamespace(java.lang.String, java.lang.String)}. * * @author Laurent Caillette */ public class SyntacticTreeNavigator extends DefaultNavigator { private static final SyntacticTreeNavigator INSTANCE = new SyntacticTreeNavigator() ; public static SyntacticTreeNavigator getInstance() { return INSTANCE ; } @Override public boolean isDocument( final Object object ) { return isDocument( asTreepath( object ).getTreeAtEnd().getNodeKind() ); } @Override public boolean isElement( final Object object ) { final NodeKind nodeKind = asTreepath( object ).getTreeAtEnd().getNodeKind() ; return nodeKind != null && ! isDocument( nodeKind ) ; } @Override public boolean isAttribute( final Object object ) { return false ; } @Override public boolean isNamespace( final Object object ) { return false ; } @Override public boolean isComment( final Object object ) { return false ; } @Override public boolean isText( final Object object ) { return asTreepath( object ).getTreeAtEnd().getNodeKind() == null ; } @Override public boolean isProcessingInstruction( final Object object ) { return false ; } @Override public String getNamespacePrefix( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getElementName( final Object object ) { return tokenNameAsXmlElementName( asTreepath( object ).getTreeAtEnd().getNodeKind().name() ) ; } @Override public String getElementNamespaceUri( final Object object ) { // return XmlNamespaces.TREE_NAMESPACE_URI ; return null ; } /** * Returns the text of this element, aggregated with the text of subelements. */ @Override public String getElementStringValue( final Object object ) { final StringBuilder stringBuilder = new StringBuilder() ; final Treepath< SyntacticTree > treepath = asTreepath( object ) ; final SyntacticTree tree = treepath.getTreeAtEnd() ; for( int i = 0 ; i < tree.getChildCount() ; i ++ ) { final SyntacticTree child = tree.getChildAt( i ) ; if( isElement( child ) ) { stringBuilder.append( getElementStringValue( Treepath.create( treepath, i ) ) ) ; } else if( isText( child ) ) { return getTextStringValue( Treepath.create( treepath, i ) ) ; } else { throw new IllegalArgumentException( "Unsupported: " + child ) ; } } return stringBuilder.toString() ; } @Override public String getElementQName( final Object object ) { return getElementName( object ) ; } @Override public String getAttributeNamespaceUri( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getAttributeName( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getAttributeQName( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getCommentStringValue( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getAttributeStringValue( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getNamespaceStringValue( final Object object ) { throw new UnsupportedOperationException( "Unexpected call" ) ; } @Override public String getTextStringValue( final Object object ) { return asTreepath( object ).getTreeAtEnd().getText() ; } @Override public Iterator getChildAxisIterator( final Object contextNode ) throws UnsupportedAxisException { // Testing both is more robust that checking #getNodeKind() != null if we add // a set of supported NodeKinds. if( isElement( contextNode ) || isDocument( contextNode ) ) { return TreepathTools.iteratorOnChildren( asTreepath( contextNode ) ) ; } return JaxenConstants.EMPTY_ITERATOR ; } @Override public Object getParentNode( final Object contextNode ) throws UnsupportedAxisException { if( isDocument( contextNode ) ) { return JaxenConstants.EMPTY_ITERATOR ; } final Treepath<SyntacticTree> treepath = asTreepath( contextNode ); Treepath< SyntacticTree > parent = treepath.getPrevious() ; if( parent == null ) { parent = treepath.getStart() ; if( parent.getTreeAtEnd() == treepath.getTreeAtEnd() ) { return null ; } } return parent ; } @Override public Object getDocumentNode( final Object contextNode ) { return asTreepath( contextNode ).getStart() ; } @Override public Iterator getParentAxisIterator( final Object contextNode ) throws UnsupportedAxisException { if( isDocument( contextNode ) ) { return JaxenConstants.EMPTY_ITERATOR ; } final Treepath<SyntacticTree> treepath = asTreepath( contextNode ); Treepath< SyntacticTree > parent = treepath.getPrevious() ; if( parent == null ) { parent = treepath.getStart() ; } return new SingleObjectIterator( parent ) ; } @Override public Iterator getNamespaceAxisIterator( final Object contextNode ) throws UnsupportedAxisException { throw new UnsupportedAxisException( "Namespaces not supported" ) ; } @Override public Iterator getAttributeAxisIterator( final Object contextNode ) throws UnsupportedAxisException { throw new UnsupportedAxisException( "Namespaces not supported" ) ; } @Override public XPath parseXPath( final String xpathExpression ) throws SAXPathException { return SyntacticTreeXpath.createUntypedXPath( xpathExpression ) ; } // ======= // Helpers // ======= @SuppressWarnings( { "unchecked" } ) private static Treepath<SyntacticTree> asTreepath( final Object object ) { final Treepath< SyntacticTree > treepath = ( Treepath< SyntacticTree > ) object ; return treepath; } private static boolean isDocument( final NodeKind nodeKind ) { return nodeKind == NodeKind.OPUS || nodeKind == NodeKind.NOVELLA ; } }