001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; 020 021import com.vladsch.flexmark.ast.HtmlInline; 022import com.vladsch.flexmark.ext.toc.TocBlock; 023import com.vladsch.flexmark.util.ast.Node; 024import com.vladsch.flexmark.util.ast.NodeTracker; 025import com.vladsch.flexmark.util.sequence.CharSubSequence; 026import org.apache.logging.log4j.LogManager; 027import org.apache.logging.log4j.Logger; 028import org.apache.wiki.api.core.Context; 029import org.apache.wiki.api.exceptions.PluginException; 030import org.apache.wiki.api.plugin.Plugin; 031import org.apache.wiki.markdown.nodes.JSPWikiLink; 032import org.apache.wiki.parser.PluginContent; 033import org.apache.wiki.preferences.Preferences; 034 035import java.text.MessageFormat; 036import java.util.ResourceBundle; 037 038 039/** 040 * {@link NodePostProcessorState} which further post processes plugin links. 041 */ 042public class PluginLinkNodePostProcessorState implements NodePostProcessorState< JSPWikiLink > { 043 044 private static final Logger LOG = LogManager.getLogger( PluginLinkNodePostProcessorState.class ); 045 private final Context wikiContext; 046 private final boolean m_wysiwygEditorMode; 047 048 public PluginLinkNodePostProcessorState( final Context wikiContext ) { 049 this.wikiContext = wikiContext; 050 final Boolean wysiwygVariable = wikiContext.getVariable( Context.VAR_WYSIWYG_EDITOR_MODE ); 051 m_wysiwygEditorMode = wysiwygVariable != null ? wysiwygVariable : false; 052 } 053 054 /** 055 * {@inheritDoc} 056 * 057 * @see NodePostProcessorState#process(NodeTracker, Node) 058 */ 059 @Override 060 public void process( final NodeTracker state, final JSPWikiLink link ) { 061 if( link.getText().toString().startsWith( "{TableOfContents" ) ) { 062 handleTableOfContentsPlugin( state, link ); 063 return; 064 } 065 PluginContent pluginContent = null; 066 try { 067 pluginContent = PluginContent.parsePluginLine( wikiContext, link.getUrl().toString(), -1 ); // -1 == do not generate _bounds parameter 068 // 069 // This might sometimes fail, especially if there is something which looks 070 // like a plugin invocation but is really not. 071 // 072 if( pluginContent != null ) { 073 final String pluginInvocation = pluginInvocation( link.getText().toString(), pluginContent ); 074 final HtmlInline content = new HtmlInline( CharSubSequence.of( pluginInvocation ) ); 075 pluginContent.executeParse( wikiContext ); 076 NodePostProcessorStateCommonOperations.addContent( state, link, content ); 077 } 078 } catch( final PluginException e ) { 079 LOG.info( wikiContext.getRealPage().getWiki() + " : " + wikiContext.getRealPage().getName() + " - Failed to insert plugin: " + e.getMessage() ); 080 if( !m_wysiwygEditorMode ) { 081 final ResourceBundle rbPlugin = Preferences.getBundle( wikiContext, Plugin.CORE_PLUGINS_RESOURCEBUNDLE ); 082 NodePostProcessorStateCommonOperations.makeError( state, link, MessageFormat.format( rbPlugin.getString( "plugin.error.insertionfailed" ), 083 wikiContext.getRealPage().getWiki(), 084 wikiContext.getRealPage().getName(), 085 e.getMessage() ) ); 086 } 087 } finally { 088 if( pluginContent != null ) { 089 removeLink( state, link ); 090 } 091 } 092 } 093 094 /** 095 * Return plugin execution. As plugin execution may not fire the plugin (i.e., on WYSIWYG editors), on those cases, the plugin line is returned. 096 * 097 * @param pluginMarkup plugin markup line 098 * @param pluginContent the plugin content. 099 * @return plugin execution, or plugin markup line if it wasn't executed. 100 */ 101 String pluginInvocation( final String pluginMarkup, final PluginContent pluginContent ) { 102 final String pluginInvocation = pluginContent.invoke( wikiContext ); 103 if( pluginMarkup.equals( pluginInvocation + "()" ) ) { // plugin line markup == plugin execution + "()" -> hasn't been executed 104 return pluginMarkup; 105 } else { 106 return pluginInvocation; 107 } 108 } 109 110 void handleTableOfContentsPlugin(final NodeTracker state, final JSPWikiLink link) { 111 if( !m_wysiwygEditorMode ) { 112 final ResourceBundle rb = Preferences.getBundle( wikiContext, Plugin.CORE_PLUGINS_RESOURCEBUNDLE ); 113 final HtmlInline divToc = new HtmlInline( CharSubSequence.of( "<div class=\"toc\">\n" ) ); 114 final HtmlInline divCollapseBox = new HtmlInline( CharSubSequence.of( "<div class=\"collapsebox\">\n" ) ); 115 final HtmlInline divsClosing = new HtmlInline( CharSubSequence.of( "</div>\n</div>\n" ) ); 116 final HtmlInline h4Title = new HtmlInline( CharSubSequence.of( "<h4 id=\"section-TOC\">" + // FIXME proper plugin parameters handling 117 rb.getString( "tableofcontents.title" ) + 118 "</h4>\n" ) ); 119 final TocBlock toc = new TocBlock( CharSubSequence.of( "[TOC]" ), CharSubSequence.of( "levels=1-3" ) ); 120 121 link.insertAfter( divToc ); 122 divToc.insertAfter( divCollapseBox ); 123 divCollapseBox.insertAfter( h4Title ); 124 h4Title.insertAfter( toc ); 125 toc.insertAfter( divsClosing ); 126 127 } else { 128 NodePostProcessorStateCommonOperations.inlineLinkTextOnWysiwyg( state, link, m_wysiwygEditorMode ); 129 } 130 removeLink( state, link ); 131 } 132 133 void removeLink(final NodeTracker state, final JSPWikiLink link) { 134 link.unlink(); 135 state.nodeRemoved( link ); 136 } 137 138}