/* * 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.treemangling; import java.util.Set; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import static org.novelang.parser.NodeKind.*; import org.novelang.common.SimpleTree; import org.novelang.common.SyntacticTree; import org.novelang.common.TagBehavior; import org.novelang.common.tree.Traversal; import org.novelang.common.tree.Treepath; import org.novelang.common.tree.TreepathTools; import org.novelang.designator.Tag; import org.novelang.parser.NodeKind; import org.novelang.parser.NodeKindTools; import org.novelang.rendering.RenderingTools; /** * Deals with {@link NodeKind#TAG}, {@link NodeKind#_EXPLICIT_TAG}, * {@link NodeKind#_IMPLICIT_TAG}, {@link NodeKind#_PROMOTED_TAG}. * * @author Laurent Caillette */ public class TagMangler { private TagMangler() { } /** * Tranforms {@link NodeKind#TAG} into {@link NodeKind#_EXPLICIT_TAG} * and adds {@link NodeKind#_IMPLICIT_TAG}s to * {@link NodeKind#_LEVEL} trees with a {@link NodeKind#LEVEL_TITLE}. * * @param treepath a non-null object. * @return a non-null object with updated/added nodes. */ public static Treepath< SyntacticTree > enhance( final Treepath< SyntacticTree > treepath ) { final Treepath< SyntacticTree > enhancedWithExplicitTags = enhanceWithExplicitTags( treepath ) ; return enhanceWithImplicitTags( enhancedWithExplicitTags ) ; } /** * Returns the set of values for {@link org.novelang.parser.NodeKind#_EXPLICIT_TAG}s. * * @return a non-null, possibly empty set. */ public static Set< Tag > findExplicitTags( final SyntacticTree tree ) { if( tree.isOneOf( UNTRAVERSABLE_NODES ) ) { return ImmutableSet.of() ; } if( tree.isOneOf( _EXPLICIT_TAG ) ) { return ImmutableSet.of( new Tag( tree.getChildAt( 0 ).getText() ) ) ; } final Set< Tag > tagset = Sets.newLinkedHashSet() ; for( final SyntacticTree child : tree.getChildren() ) { tagset.addAll( findExplicitTags( child ) ) ; } return tagset ; } /** * Could be expressed as a {@link NodeKind} property, maybe. */ private static final ImmutableSet< NodeKind > UNTRAVERSABLE_NODES = ImmutableSet.of( WORD_, WORD_AFTER_CIRCUMFLEX_ACCENT, _STYLE, PUNCTUATION_SIGN, _IMPLICIT_IDENTIFIER, _EXPLICIT_IDENTIFIER ) ; // ======== // Explicit // ======== @SuppressWarnings( { "AssignmentToForLoopParameter" } ) public static Treepath< SyntacticTree > enhanceWithExplicitTags( Treepath< SyntacticTree > treepath ) { for( Treepath< SyntacticTree > next = treepath ; next != null ; ) { switch( NodeKindTools.ofRoot( next.getTreeAtEnd() ).getTagBehavior() ) { case SCOPE : next = replaceByExplicitTag( next ) ; case TRAVERSABLE : next = PREORDER.next( next ) ; break; case TERMINAL : next = replaceByExplicitTag( next ) ; next = PREORDER.next( next ) ; break ; case NON_TRAVERSABLE : next = PREORDER.nextUp( next ) ; break ; } if( next != null ) { treepath = next ; } } return treepath.getStart() ; } private static final Traversal.Preorder< SyntacticTree > PREORDER = Traversal.Preorder.create() ; private static Treepath< SyntacticTree > replaceByExplicitTag( Treepath< SyntacticTree > treepathToTagOwner ) { final SyntacticTree ownerTree = treepathToTagOwner.getTreeAtEnd() ; for( int i = 0 ; i < ownerTree.getChildCount() ; i ++ ) { final SyntacticTree child = ownerTree.getChildAt( i ) ; if( child.isOneOf( NodeKind.TAG ) ) { final SyntacticTree explicitTag = new SimpleTree( NodeKind._EXPLICIT_TAG, new SimpleTree( child.getChildAt( 0 ).getText() ) ) ; final Treepath< SyntacticTree > treepathToTag = Treepath.create( treepathToTagOwner, i ) ; treepathToTagOwner = TreepathTools.replaceTreepathEnd( treepathToTag, explicitTag ). getPrevious() ; } } return treepathToTagOwner ; } // ======== // Implicit // ======== @SuppressWarnings( { "AssignmentToForLoopParameter" } ) public static Treepath< SyntacticTree > enhanceWithImplicitTags( Treepath< SyntacticTree > treepath ) { for( Treepath< SyntacticTree > next = treepath ; next != null ; ) { switch( NodeKindTools.ofRoot( treepath.getTreeAtEnd() ) ) { case _LEVEL : if( ! hasTag( next ) ) { final Set< Tag > tagset = findImplicitTags( next ) ; if( ! tagset.isEmpty() ) { next = addImplicitTags( next, tagset ) ; } } case NOVELLA: case OPUS: // TODO replace by Traversal.getFirst() next = PREORDER.next( next ) ; break ; default : next = PREORDER.nextUp( next ) ; } if( next != null ) { treepath = next ; } } return treepath.getStart() ; } private static Treepath< SyntacticTree > addImplicitTags( Treepath< SyntacticTree > treepathToLevel, final Set< Tag > tagset ) { for( final Tag tag : tagset ) { treepathToLevel = TreepathTools.addChildFirst( treepathToLevel, tag.asSyntacticTree( NodeKind._IMPLICIT_TAG ) ).getPrevious() ; } return treepathToLevel ; } private static boolean hasTag( final Treepath< SyntacticTree > treepathToLevel ) { final SyntacticTree ownerTree = treepathToLevel.getTreeAtEnd() ; for( int i = 0 ; i < ownerTree.getChildCount() ; i ++ ) { final SyntacticTree child = ownerTree.getChildAt( i ) ; if( child.isOneOf( NodeKind._EXPLICIT_TAG, NodeKind.TAG ) ) { return true ; } } return false ; } private static Set< Tag > findImplicitTags( final Treepath< SyntacticTree > treepathToLevel ) { final SyntacticTree ownerTree = treepathToLevel.getTreeAtEnd() ; for( int i = 0 ; i < ownerTree.getChildCount() ; i ++ ) { final SyntacticTree child = ownerTree.getChildAt( i ) ; if( child.isOneOf( NodeKind.LEVEL_TITLE ) ) { return RenderingTools.toImplicitTagSet( child ) ; } } return ImmutableSet.of() ; } // ======== // Promoted // ======== /** * Replaces {@link NodeKind#_IMPLICIT_TAG}s appearing in the given tag set * by {@link NodeKind#_PROMOTED_TAG}s. * * @see #promote(Treepath, Set) */ @SuppressWarnings( { "AssignmentToForLoopParameter" } ) public static Treepath< SyntacticTree > promote( Treepath< SyntacticTree > treepath, final Set< Tag > explicitTags ) { if( treepath.getTreeAtStart().getChildCount() == 0 ) { return treepath.getStart() ; } for( Treepath< SyntacticTree > next = treepath ; next != null ; ) { final SyntacticTree tree = next.getTreeAtEnd() ; final NodeKind nodeKind = NodeKindTools.ofRoot( tree ) ; switch( nodeKind ) { case NOVELLA: case OPUS: // TODO replace by Traversal.getFirst() next = PREORDER.next( next ) ; break ; default : if ( tree.isOneOf( _IMPLICIT_TAG ) ) { final Tag implicitTag = new Tag( tree.getChildAt( 0 ).getText() ) ; if ( explicitTags.contains( implicitTag ) ) { final SyntacticTree promotedTag = new SimpleTree( _PROMOTED_TAG, tree.getChildren() ) ; next = TreepathTools.replaceTreepathEnd( treepath, promotedTag ) ; } else { next = PREORDER.nextUp( next ) ; } } else if( nodeKind.getTagBehavior() == TagBehavior.NON_TRAVERSABLE ) { next = PREORDER.nextUp( next ) ; } else { next = PREORDER.next( next ) ; } } if( next != null ) { treepath = next ; } } return treepath.getStart() ; } }