/* 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. */ /* TablePlugin Dirk Frederickx v.001 Feb 2005 v.002 Mar 2005 : bugfix, nested plugins, sorting, cell-level styles v.003 Jun 2006 : remove escape stuff, jspwiki now supports nested templates v.004 Dec 2013 : Apache JSPWiki version, incl. bugfix in startsWith() */ package brushed.jspwiki.tableplugin; import org.apache.log4j.Logger; import org.apache.wiki.WikiContext; import org.apache.wiki.api.exceptions.PluginException; import org.apache.wiki.api.plugin.WikiPlugin; import org.apache.wiki.util.TextUtil; import java.util.Map; import java.util.List; import java.util.ArrayList; /** * Extend JSPWiki syntax for tables * *

Parameters

* * * @author Dirk Frederickx */ public class Table implements WikiPlugin { private static Logger log = Logger.getLogger( Table.class ); private WikiContext m_context; private StringBuffer m_result = new StringBuffer(); private String m_style ; private String m_style_header ; private String m_style_data ; private String m_styleRowEven ; private String m_styleRowOdd ; private int m_startRow; public static final String PARAM_BODY = "_body"; public static final String PARAM_STYLE = "style"; public static final String PARAM_STYLE_HEADER = "headerStyle"; public static final String PARAM_STYLE_DATA = "dataStyle"; public static final String PARAM_STYLE_ROW_ODD = "evenRowStyle"; public static final String PARAM_STYLE_ROW_EVEN = "oddRowStyle"; public static final String PARAM_ROW_NUMBER = "rowNumber"; public String execute( WikiContext context, Map params ) throws PluginException { m_context = context; if( context.getPage() == null ) return ""; // parse parameters String body = params.get( PARAM_BODY ); if( body == null ) return "" ; m_style = params.get( PARAM_STYLE ); m_style_header = params.get( PARAM_STYLE_HEADER ); m_style_data = params.get( PARAM_STYLE_DATA ); m_styleRowEven = params.get( PARAM_STYLE_ROW_EVEN ); m_styleRowOdd = params.get( PARAM_STYLE_ROW_ODD ); m_startRow = TextUtil.parseIntParameter( params.get( PARAM_ROW_NUMBER ), 0 ); log.info( "TABLE plugin invoked\n" ); m_result = new StringBuffer( "" ); processTable( body ); return m_result.toString() ; } /** * Actual processing of the table rows * */ protected void processTable(String aBody) { StringBuffer sb = new StringBuffer( aBody ); int i = 0; //running index in body string char bi; //character at index i in body string int bLen = sb.length(); //length of body string boolean opaqueCode = false; boolean opaquePre = false; boolean opaqueLink = false; boolean opaqueCSS = false; int opaquePlugin = 0; final int S_IDLE = 0; final int S_IDLE_BLANK = 1; final int S_ONE_CELL = 2; final int S_STANDARD_ROW = 3; final int S_MULTILINE_ROW = 4; final int S_MULTILINE_ROW_DELIM = 5; int state = S_IDLE; TableCell cell = null; List row = new ArrayList(); List> table = new ArrayList>(); while( i < bLen ) //while not beyond end of file { bi = sb.charAt(i); if( cell == null ) { if( bi == '\n' ) { state = S_IDLE; } else if( Character.isWhitespace(bi) ) { state = S_IDLE_BLANK; } else if( (bi == '|') && (state == S_IDLE) ) // cell at start of line { cell = new TableCell(); i = cell.registerStart( sb, i ); row.add( cell ); state = S_ONE_CELL; } else // char outside a table cell { log.info("ERR: CHAR outside table \"" + bi + "\"" ); break; } } else //cell != null : process data inside a cell { if( startsWith("[{", sb, i) ) { opaquePlugin++; i++;} else if( startsWith("}]", sb, i) ) { opaquePlugin--; i++; } else if( opaquePlugin > 0 ) { } else if( opaqueCode ) // skip {{{ code-block }}} { if( startsWith("}}}", sb, i) ) { opaqueCode = false; i+= 2; } } else if( startsWith("{{{", sb, i) ) { opaqueCode = true; i+= 2; } else if( opaquePre ) // skip {{ preformatted text }} { if( startsWith("}}", sb, i) ) { opaquePre = false; i++; } } else if( startsWith("{{", sb, i) ) { opaquePre = true; i++; } else if( opaqueCSS ) // skip %% css styles %% { if( startsWith("%%", sb, i) ) { opaqueCSS = false; i++; } } else if( startsWith("%%", sb, i) ) { opaqueCSS = true; i++; } else if( opaqueLink ) // skip [text | opaqueLinks] { if( bi == ']' ) { opaqueLink = false; } } else if( bi == '[') { opaqueLink = true; } else if( cell.cssStyle ) { cell.handleCss( sb, i ); } else if( startsWith( "~#", sb, i) ) //escape the # { i++; } else if( bi == '#' ) { sb.deleteCharAt( i ); sb.insert( i, table.size() + m_startRow ); bLen = sb.length(); } else if( startsWith( "~|", sb, i) ) //escape the # { i++; } /* Pseudo idle | => start new cell; mode=one_cell \n => mode=idle blank => mode=idle blank else idle_blank \n => mode=idle blank => mode=idle blank else one_cell \n| => end prev cell; start new cell ; mode=multiline row | => end prev cell; start new cell ; mode=standard row \n => mode=multiline row delim standard-row | => end prev cell; start new cell ; mode=standard row \n => end prev cell; row++ ; mode=idle; multiline_row \n| => end prev cell; start new cell; mode=multiline row \n => mode = multiline row delim multiline_row_delim \n => end prev cell; row++ ; mode=idle; non-blank => mode=multiline row */ //this is independent of \r\n of \n (0D0A) else if( (startsWith( "\n|", sb, i )) && ((state == S_ONE_CELL) || (state == S_MULTILINE_ROW)) ) { cell.registerEnd( sb, i-1 ); cell = new TableCell(); i = cell.registerStart( sb, i+1 ); row.add( cell ); state = S_MULTILINE_ROW; } else if( (bi == '|') && ((state == S_ONE_CELL) || (state == S_STANDARD_ROW)) ) { cell.registerEnd( sb, i ); cell = new TableCell(); i = cell.registerStart( sb, i ); row.add( cell ); state = S_STANDARD_ROW; } else if( (bi == '\n') && ((state == S_ONE_CELL) || (state == S_MULTILINE_ROW)) ) { state = S_MULTILINE_ROW_DELIM; } else if( (bi == '\n') && ((state == S_STANDARD_ROW) || (state == S_MULTILINE_ROW_DELIM)) ) { cell.registerEnd( sb, i ); table.add( row ); row = new ArrayList(); cell = null; state = S_IDLE; } else if( (state == S_MULTILINE_ROW_DELIM) && ( !Character.isWhitespace(bi) ) ) { state = S_MULTILINE_ROW; } } // cell != null i++; // take next char } if( cell != null ) { cell.registerEnd( sb, i ); table.add( row ); } processTableFlush( table, sb ); } // - - - - - - - - - - - - - - - - - - - - - - - /** * Append HTML table to output * */ protected void processTableFlush( List> aTable, StringBuffer aBody ) { if( aTable.isEmpty() ) return; m_result.append( " \n"); for( int r=0; r < aTable.size(); r++ ) { List row = aTable.get(r); m_result.append( "\n" ); for( int c=0; c < row.size(); c++ ) //Iterator c = row.iterator(); c.hasNext(); ) { TableCell cell = row.get(c); if( cell.colspan ) continue; //this cell is collapsed with previous col if( cell.rowspan ) continue; //this cell is collpased with previous row if( cell.isHeader) { m_result.append( "1 ) m_result.append( " colspan='" + colspan + "'" ); int rowspan = getRowSpan( aTable, r, c ); if( rowspan>1 ) m_result.append( " rowspan='" + rowspan + "'" ); m_result.append( ">" ); m_result.append( m_context.getEngine().textToHTML( m_context, aBody.substring( cell.start, cell.end ) ) ); if( cell.isHeader ) { m_result.append( "\n" ); } else { m_result.append( "\n" ); } } m_result.append( "\n" ); row.clear(); } aTable.clear(); m_result.append( "
\n" ); } /** * Calculate ColSpan by looking at next cells on the same row * */ protected int getColSpan( List> aTable, int rowIndex, int columnIndex ) { int colspan = 1; List row = aTable.get(rowIndex); while( ++columnIndex < row.size() ) { TableCell nextCell = row.get(columnIndex); if( ! nextCell.colspan ) break; colspan++; } return colspan; } /** * Calculate RowSpan by looking at next rows * */ protected int getRowSpan( List> aTable, int rowIndex, int columnIndex ) { int rowspan = 1; while( ++rowIndex < aTable.size() ) { List nextRow = aTable.get(rowIndex); if( columnIndex < nextRow.size() ) { TableCell nextRowCell = nextRow.get(columnIndex); if( ! nextRowCell.rowspan ) break; rowspan++; } } return rowspan; } /** * Helper routine : startsWith * This mimics the String.startsWith function on StringBuffer. * Tests if this string starts with the specified prefix beginning a specified index. * * @param String prefix : target string to match * @param StringBuffer sb : stringbuffer to search for the specified prefix * @param int fromIndex : start search from the fromIndex * * @return boolean * */ protected boolean startsWith( String prefix, StringBuffer sb, int fromIndex ) { return ( sb.indexOf(prefix, fromIndex) == fromIndex ); } // local class definition -- storage container for a table cell private class TableCell { public int start; public int end; public boolean isHeader; public boolean colspan; public boolean rowspan; public boolean cssStyle; public int cssBracket; public int cssStart; public int cssEnd; public int registerStart( StringBuffer sb, int aCursor ) { end = -1; cssStart = -1; cssEnd = -1; cssStyle = false; cssBracket = 0; colspan = false; rowspan = false; isHeader = false; if( aCursor + 1 < sb.length() ) //parse also next char { char c = sb.charAt( aCursor + 1 ); if( c == '|' ) { isHeader = true; aCursor++; } } if( aCursor + 1 < sb.length() ) //parse also next char { char c = sb.charAt( aCursor + 1 ); if( c == '<' ) { colspan = true; aCursor++; } else if( c == '^' ) { rowspan = true; aCursor++; } else if( c == '(' ) { cssStyle = true; cssBracket=1; aCursor++; } } start = aCursor + 1; return aCursor; } /** * Parse cell with css-style: |(css-style) ... * Take care, the css style may contain nested () * */ public void handleCss( StringBuffer sb, int aCursor ) { char c = sb.charAt(aCursor); if( c == '(' ) { cssBracket++; } else if( c == ')' ) { cssBracket--; } ; if( cssBracket == 0 ) { cssStyle = false; cssStart = start ; // skip |( cssEnd = aCursor; start = cssEnd + 1 ; } } /** * Register the end of a table cell * */ public TableCell registerEnd( StringBuffer sb, int aCursor ) { end = aCursor ; log.debug("Cell [" + sb.substring(start,end) + "]" ) ; return this; } } }