// Copyright 2004 Bradford Holcombe. All rights reserved. package com.bradfordholcombe.JSPWiki.filters; import com.ecyrd.jspwiki.WikiContext; import com.ecyrd.jspwiki.filters.BasicPageFilter; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.SortedSet; import java.util.TreeSet; /** Breaks up a list of <ul> lists into column format. Thread-safe. @author Bradford Holcombe */ public class ColumnFilter extends BasicPageFilter { /** * Wikiname of the page to apply this filter to. */ private String pageName; /** * if true, sort list entries. */ private boolean sort = false; /** * Number of columns to create. */ private int columnCount = DEFAULT_COLUMN_COUNT_PROPERTY; /** * Name of the wiki page name property */ private static final String PAGE_TITLE_PROPERTY = "pageTitle"; /** * Name of the number of columns property. */ private static final String COLUMN_COUNT_PROPERTY = "columnCount"; /** * Name of the sort property. */ private static final String SORT_PROPERTY = "sort"; /** * Default number of columns. */ private static final int DEFAULT_COLUMN_COUNT_PROPERTY = 5; /** * @see com.ecyrd.jspwiki.filters.PageFilter#initialize(java.util.Properties) */ public final void initialize( final Properties props ) { pageName = (String)props.get( PAGE_TITLE_PROPERTY ); if( props.containsKey( COLUMN_COUNT_PROPERTY ) ) { //System.out.println( props.get( COLUMN_COUNT_PROPERTY ) ); columnCount = Integer.valueOf( (String)props.get( COLUMN_COUNT_PROPERTY ) ).intValue(); } if( props.containsKey( SORT_PROPERTY ) ) { //System.out.println( props.get( SORT_PROPERTY ) ); sort = Boolean.valueOf( (String)props.get( SORT_PROPERTY ) ).booleanValue(); } //System.out.println( "ColumnFilter:" + this + " " + pageName + " " + columnCount + " " + sort ); } /** * @see com.ecyrd.jspwiki.filters.PageFilter#preTranslate(com.ecyrd.jspwiki.WikiContext, java.lang.String) */ public final String preTranslate( final WikiContext wikiContext, final String wikiContent ) { if( !sort ) { return wikiContent; } if( wikiContext != null && !wikiContext.getPage().getName().equals( pageName ) ) { return wikiContent; } int listStart = wikiContent.indexOf( "*" ); int listEnd = wikiContent.length(); if( listStart == -1 ) { return wikiContent; } String originalList = wikiContent.substring( listStart, listEnd ); String newList = sort( originalList ); StringBuffer newPage = new StringBuffer(); newPage.append( wikiContent ); newPage.replace( listStart, listEnd, newList ); return newPage.toString(); } /** * Return the wiki list alphabetised and sectioned. * * @param list * @return */ private final String sort( final String list ) { Comparator comparator = new Comparator() { public final int compare( final Object o1, final Object o2 ) { if( !( o1 instanceof String ) || !( o2 instanceof String ) ) { throw new ClassCastException( "Can't compare " + o1.getClass().getName() ); } return normalizeListItem( (String)o1 ).compareTo( normalizeListItem( (String)o2 ) ); } }; SortedSet itemSet = new TreeSet( comparator ); int cursor = 0; int listLength = list.length(); while( cursor <= listLength ) { int lineEnd = list.indexOf( "\n", cursor + 1 ); if( lineEnd == -1 ) { lineEnd = list.length(); } String line = list.substring( cursor, lineEnd ); itemSet.add( line + "\n" ); cursor += line.length() + 1; } StringBuffer newList = new StringBuffer(); char section = '?'; for( Iterator sorter = itemSet.iterator(); sorter.hasNext(); ) { String line = (String)sorter.next(); String normalLine = normalizeListItem( line ); char sectionChar = normalLine.charAt( 0 ); if( sectionChar >= '0' && sectionChar <= '9' ) { if( section == '?' ) { newList.append( "!0-9\n" ); section = '0'; } } else if( sectionChar > section ) { newList.append( "!" ).append( Character.toUpperCase( sectionChar ) ).append( "\n" ); section = sectionChar; } newList.append( line ); } return newList.toString(); } /** * Returns the alphabetical sort normalized version of a string. * * @param s String to normalize * @return Normalized string */ private final String normalizeListItem( final String s ) { String s1 = s.toLowerCase(); if( s1.startsWith( "*" ) ) { s1 = s1.substring( 1 ); } if( s1.startsWith( "[" ) ) { s1 = s1.substring( 1 ); } if( s1.startsWith( "the " ) ) { s1 = s1.substring( 4 ); } if( s1.startsWith( "a " ) ) { s1 = s1.substring( 2 ); } if( s1.startsWith( "an " ) ) { s1 = s1.substring( 3 ); } if( s1.startsWith( "\"" ) ) { s1 = s1.substring( 1 ); } return s1; } /** * This method is called after a page has been fed through the TranslatorReader, * so anything you are seeing here is translated content. * * @param wikiContext The current wikicontext. * @param htmlContent WikiMarkup. * @return Translated content. * @see com.ecyrd.jspwiki.filters.PageFilter#postTranslate */ public final String postTranslate( final WikiContext wikiContext, final String htmlContent ) { if( wikiContext != null && !wikiContext.getPage().getName().equals( pageName ) ) { return htmlContent; } if( columnCount == 1 ) { return htmlContent; } int columnPercent = 100 / columnCount; int listStart = findListStart( htmlContent ); int listEnd = findListEnd( htmlContent ); if( listStart == -1 || listEnd == -1 ) { return htmlContent; } String originalList = htmlContent.substring( listStart, listEnd ); // add CSS class originalList = addClassAttributes( originalList ); String lineAccurateList = normalize( originalList ); String newList = breakup( lineAccurateList, columnPercent ); StringBuffer newPage = new StringBuffer(); newPage.append( htmlContent ); newPage.replace( listStart, listEnd, newList ); return newPage.toString(); } /** * Add a class attribute to all important tags. * * @param content Orignal HTML * @return tagged HTML */ private final String addClassAttributes( final String content ) { StringBuffer result = new StringBuffer( content ); int cursor = result.indexOf( "
" ); cursor = result.indexOf( "

", cursor ); while( cursor > -1 ) { result.replace(cursor, cursor+4, "

"); cursor = result.indexOf( "

", cursor + "class=\"listheading\"".length()); } return result.toString(); } /** * Returns the location of the beginning of the list. * * @param content String to find the list start in. * @return Start location. */ private final int findListStart( final String content ) { return content.indexOf( "

" ); } /** * Returns the location of the end of the list. * * @param content String to find the list end in. * @return End location. */ private final int findListEnd( final String content ) { int end = content.lastIndexOf( "

" ); end = content.indexOf( "", end + 1 ) + 5; return end; } /** * Returns the number of lines that this list will be in HTML. * * @param list List to count lines in * @return Line count */ private final int countLines( final String list ) { int result = 0; int cursor = 0; int listLength = list.length(); while( cursor <= listLength ) { int lineEnd = list.indexOf( "\n", cursor + 1 ); if( lineEnd == -1 ) { lineEnd = list.length(); } String line = list.substring( cursor, lineEnd ); result++; cursor += line.length() + 1; } return result; } /** * Returns the character position for the requested line * * @param lineNumber line to find * @param list List to look in * @return Character position of line start */ private final int getCharForLine( final int lineNumber, final String list ) { int result = 0; int cursor = 0; int listLength = list.length(); while( cursor <= listLength ) { int lineEnd = list.indexOf( "\n", cursor + 1 ); if( lineEnd == -1 ) { lineEnd = list.length(); } String line = list.substring( cursor, lineEnd ); if( result == lineNumber ) { return cursor; } result++; cursor += line.length() + 1; } return result; } /** * Returns the list with a line accurate representation. * * @param list List to normalize * @return new list */ private final String normalize( final String list ) { StringBuffer newList = new StringBuffer(); newList.append( list ); int cursor = 0; int nextChange = newList.indexOf( "\n", cursor ); while( nextChange != -1 ) { newList.replace( nextChange, nextChange + 6, "" ); cursor = nextChange + 1; nextChange = newList.indexOf( "\n", cursor ); } return newList.toString(); } /** * Returns the section lengths of the HTML list. * * @param list List to find sections in * @return Line count of sections array */ private final int[] findSections( final String list ) { List sectionList = new ArrayList(); int cursor = 0; int listLength = list.length(); int lineCount = 0; while( cursor <= listLength ) { int lineEnd = list.indexOf( "\n", cursor + 1 ); if( lineEnd == -1 ) { lineEnd = list.length(); } String line = list.substring( cursor, lineEnd ); if( line.startsWith( " 0 ) { sectionList.add( new Integer( lineCount ) ); } cursor += line.length() + 1; lineCount++; } int[] resultList = new int[sectionList.size()]; for( int i = 0; i < sectionList.size(); i++ ) { Integer s = (Integer)sectionList.get( i ); resultList[ i ] = s.intValue(); } return resultList; } /** * Break up the list into a number of columns. * * @param list List to break up. * @param columnPercent * @return Broken-up list. */ private final String breakup( final String list, final int columnPercent ) { int lineCount = countLines( list ); int columnLines = lineCount / columnCount; int[] sectionBoundaries = findSections( list ); if( sectionBoundaries == null || sectionBoundaries.length == 0 ) { return list; } int[] breaks = new int[columnCount]; Arrays.fill( breaks, -1 ); int lastBoundary = sectionBoundaries[ 0 ]; int column = 0; int breakPosition; for( int sectionIndex = 0; sectionIndex < sectionBoundaries.length; sectionIndex++ ) { breakPosition = columnLines * ( column + 1 ); if( sectionBoundaries[ sectionIndex ] >= breakPosition ) { if( ( sectionBoundaries[ sectionIndex ] - breakPosition ) < ( breakPosition - lastBoundary ) ) { breaks[ column ] = sectionBoundaries[ sectionIndex ]; } else { breaks[ column ] = lastBoundary; } column++; } lastBoundary = sectionBoundaries[ sectionIndex ]; } StringBuffer newList = new StringBuffer(); newList.append( list ); int linesAdded = 0; for( int breakIndex = 0; breakIndex < breaks.length; breakIndex++ ) { if( breaks[ breakIndex ] == -1 ) { break; } newList.insert( getCharForLine( breaks[ breakIndex ] + linesAdded, newList.toString() ), "

\n
\n" ); linesAdded += 2; } newList .insert( 0, "
\n
\n" ); newList.append( "
\n
\n" ); return newList.toString(); } /** * Test the filter. * * @param args Command line args. */ public static void main( String[] args ) { /*try { System.out.println( new File( "." ).getAbsolutePath() ); BufferedReader in = new BufferedReader( new FileReader( "docs/source.html" ) ); StringBuffer input = new StringBuffer(); String line = in.readLine(); while( line != null ) { input.append( line ).append( '\n' ); line = in.readLine(); } ColumnFilter filter = new ColumnFilter(); String result = filter.postTranslate( null, input.toString() ); System.out.println( result ); } catch( IOException ioe ) { System.out.println( ioe ); }*/ try { System.out.println( new File( "." ).getAbsolutePath() ); BufferedReader in = new BufferedReader( new FileReader( "docs/sort.txt" ) ); StringBuffer input = new StringBuffer(); String line = in.readLine(); while( line != null ) { input.append( line ).append( '\n' ); line = in.readLine(); } ColumnFilter filter = new ColumnFilter(); String result = filter.preTranslate( null, input.toString() ); System.out.println( result ); //result = filter.postTranslate( null, result ); //System.out.println( result ); } catch( IOException ioe ) { System.out.println( ioe ); } } }