001package org.apache.maven.doxia.module.twiki.parser;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.doxia.util.ByLineSource;
023import org.apache.maven.doxia.parser.ParseException;
024import org.apache.maven.doxia.sink.Sink;
025
026import java.util.ArrayList;
027import java.util.List;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031/**
032 * Generic list parser
033 *
034 * @author Juan F. Codagnone
035 * @version $Id: GenericListBlockParser.html 905940 2014-04-12 16:27:29Z hboutemy $
036 */
037public class GenericListBlockParser
038    implements BlockParser
039{
040    static final String EOL = System.getProperty( "line.separator" );
041
042    /**
043     * parser used to create text blocks
044     */
045    private FormatedTextParser formatedTextParser;
046
047    /**
048     * supported patterns
049     */
050    private final Pattern[] patterns = new Pattern[TYPES.length];
051
052    /**
053     * Creates the GenericListBlockParser.
054     */
055    public GenericListBlockParser()
056    {
057        for ( int i = 0; i < TYPES.length; i++ )
058        {
059            patterns[i] = Pattern.compile( "^((   )+)" + TYPES[i].getItemPattern() + "(.*)$" );
060        }
061    }
062
063    /** {@inheritDoc} */
064    public final boolean accept( final String line )
065    {
066        boolean ret = false;
067
068        for ( int i = 0; !ret && i < patterns.length; i++ )
069        {
070            ret |= patterns[i].matcher( line ).lookingAt();
071        }
072
073        return ret;
074    }
075
076    /**
077     * {@inheritDoc}
078     */
079    public final Block visit( final String line, final ByLineSource source )
080        throws ParseException
081    {
082        final TreeListBuilder treeListBuilder = new TreeListBuilder( formatedTextParser );
083        // new TreeListBuilder(formatedTextParser);
084        String l = line;
085        do
086        {
087            if ( !accept( l ) )
088            {
089                break;
090            }
091
092            for ( int i = 0; i < patterns.length; i++ )
093            {
094                final Matcher m = patterns[i].matcher( l );
095                if ( m.lookingAt() )
096                {
097                    final int numberOfSpaces = 3;
098                    final int textGroup = 3;
099                    assert m.group( 1 ).length() % numberOfSpaces == 0;
100                    final int level = m.group( 1 ).length() / numberOfSpaces;
101                    treeListBuilder.feedEntry( TYPES[i], level, m.group( textGroup ).trim() );
102                    break;
103                }
104            }
105        }
106        while ( ( l = source.getNextLine() ) != null );
107
108        if ( l != null )
109        {
110            source.ungetLine();
111        }
112
113        return treeListBuilder.getBlock();
114    }
115
116    /**
117     * Sets the formatTextParser.
118     *
119     * @param textParser <code>FormatedTextParser</code> with the formatTextParser.
120     */
121    public final void setTextParser( final FormatedTextParser textParser )
122    {
123        if ( textParser == null )
124        {
125            throw new IllegalArgumentException( "formatTextParser can't be null" );
126        }
127        this.formatedTextParser = textParser;
128    }
129
130    interface Type
131    {
132        /**
133         * @return the pattern of the item part of the list regex
134         */
135        String getItemPattern();
136
137        /**
138         * @param items children of the new listblock
139         * @return a new ListBlock
140         */
141        ListBlock createList( final ListItemBlock[] items );
142
143    }
144
145    /**
146     * unordered list
147     */
148    private static final Type LIST = new Type()
149    {
150        /** {@inheritDoc} */
151        public String getItemPattern()
152        {
153            return "[*]";
154        }
155
156        /** {@inheritDoc} */
157        public ListBlock createList( final ListItemBlock[] items )
158        {
159            return new UnorderedListBlock( items );
160        }
161    };
162
163    /**
164     * a.
165     */
166    private static final Type ORDERED_LOWER_ALPHA = new Type()
167    {
168        /** {@inheritDoc} */
169        public String getItemPattern()
170        {
171            return "[a-hj-z][.]";
172        }
173
174        /** {@inheritDoc} */
175        public ListBlock createList( final ListItemBlock[] items )
176        {
177            return new NumeratedListBlock( Sink.NUMBERING_LOWER_ALPHA, items );
178        }
179    };
180
181    /**
182     * A.
183     */
184    private static final Type ORDERED_UPPER_ALPHA = new Type()
185    {
186        /** {@inheritDoc} */
187        public String getItemPattern()
188        {
189            return "[A-HJ-Z][.]";
190        }
191
192        /** {@inheritDoc} */
193        public ListBlock createList( final ListItemBlock[] items )
194        {
195            return new NumeratedListBlock( Sink.NUMBERING_UPPER_ALPHA, items );
196        }
197    };
198
199    /**
200     * 1.
201     */
202    private static final Type ORDERERED_DECIMAL = new Type()
203    {
204        /** {@inheritDoc} */
205        public String getItemPattern()
206        {
207            return "[0123456789][.]";
208        }
209
210        /** {@inheritDoc} */
211        public ListBlock createList( final ListItemBlock[] items )
212        {
213            return new NumeratedListBlock( Sink.NUMBERING_DECIMAL, items );
214        }
215    };
216
217    /**
218     * i.
219     */
220    private static final Type ORDERERED_LOWER_ROMAN = new Type()
221    {
222        /** {@inheritDoc} */
223        public String getItemPattern()
224        {
225            return "[i][.]";
226        }
227
228        /** {@inheritDoc} */
229        public ListBlock createList( final ListItemBlock[] items )
230        {
231            return new NumeratedListBlock( Sink.NUMBERING_LOWER_ROMAN, items );
232        }
233    };
234
235    /**
236     * I.
237     */
238    private static final Type ORDERERED_UPPER_ROMAN = new Type()
239    {
240        /** {@inheritDoc} */
241        public String getItemPattern()
242        {
243            return "[I][.]";
244        }
245
246        /** {@inheritDoc} */
247        public ListBlock createList( final ListItemBlock[] items )
248        {
249            return new NumeratedListBlock( Sink.NUMBERING_UPPER_ROMAN, items );
250        }
251    };
252
253    private static final Type[] TYPES =
254        { LIST, ORDERED_LOWER_ALPHA, ORDERED_UPPER_ALPHA, ORDERERED_DECIMAL, ORDERERED_LOWER_ROMAN,
255            ORDERERED_UPPER_ROMAN };
256
257}
258
259/**
260 * It helps to build
261 *
262 * @author Juan F. Codagnone
263 * @version $Id: GenericListBlockParser.html 905940 2014-04-12 16:27:29Z hboutemy $
264 */
265class TreeListBuilder
266{
267    /**
268     * parser that create text blocks
269     */
270    private final FormatedTextParser textParser;
271
272    /**
273     * tree root
274     */
275    private final TreeComponent root;
276
277    /**
278     * the current element of the tree
279     */
280    private TreeComponent current;
281
282    /**
283     * Creates the TreeListBuilder.
284     *
285     * @param formatTextParser parser that create text blocks
286     * @throws IllegalArgumentException if <code>formatTextParser</code> is null
287     */
288    TreeListBuilder( final FormatedTextParser formatTextParser )
289        throws IllegalArgumentException
290    {
291        if ( formatTextParser == null )
292        {
293            throw new IllegalArgumentException( "argument is null" );
294        }
295        this.textParser = formatTextParser;
296        root = new TreeComponent( null, "root", null );
297        current = root;
298    }
299
300    /**
301     * recibe un nivel y un texto y armar magicamente (manteniendo estado)
302     * el �rbol
303     *
304     * @param type  type of list
305     * @param level indentation level of the item
306     * @param text  text of the item
307     */
308    void feedEntry( final GenericListBlockParser.Type type, final int level, final String text )
309    {
310        final int currentDepth = current.getDepth();
311        final int incomingLevel = level - 1;
312
313        if ( incomingLevel == currentDepth )
314        {
315            // nothing to move
316        }
317        else if ( incomingLevel > currentDepth )
318        {
319            // el actual ahora es el �ltimo que insert�
320            final TreeComponent[] components = current.getChildren();
321            if ( components.length == 0 )
322            {
323                /* for example:
324                 *        * item1
325                 *     * item2
326                 */
327                for ( int i = 0, n = incomingLevel - currentDepth; i < n; i++ )
328                {
329                    current = current.addChildren( "", type );
330                }
331            }
332            else
333            {
334                current = components[components.length - 1];
335            }
336
337        }
338        else
339        {
340            for ( int i = 0, n = currentDepth - incomingLevel; i < n; i++ )
341            {
342                current = current.getFather();
343                if ( current == null )
344                {
345                    throw new IllegalStateException();
346                }
347            }
348        }
349        current.addChildren( text, type );
350    }
351
352    /**
353     * @return a Block for the list that we received
354     */
355    ListBlock getBlock()
356    {
357        return getList( root );
358    }
359
360    /**
361     * Wrapper
362     *
363     * @param tc tree
364     * @return list Block for this tree
365     */
366    private ListBlock getList( final TreeComponent tc )
367    {
368        ListItemBlock[] li = getListItems( tc ).toArray( new ListItemBlock[] {} );
369        return tc.getChildren()[0].getType().createList( li );
370    }
371
372    /**
373     * @param tc tree
374     * @return list Block for this tree
375     */
376    private List<ListItemBlock> getListItems( final TreeComponent tc )
377    {
378        final List<ListItemBlock> blocks = new ArrayList<ListItemBlock>();
379
380        for ( int i = 0; i < tc.getChildren().length; i++ )
381        {
382            final TreeComponent child = tc.getChildren()[i];
383
384            Block[] text = new Block[] {};
385            if ( child.getFather() != null )
386            {
387                text = textParser.parse( child.getText() );
388            }
389
390            if ( child.getChildren().length != 0 )
391            {
392                blocks.add( new ListItemBlock( text, getList( child ) ) );
393            }
394            else
395            {
396                blocks.add( new ListItemBlock( text ) );
397            }
398        }
399
400        return blocks;
401    }
402
403    /**
404     * A bidirectional tree node
405     *
406     * @author Juan F. Codagnone
407     * @version $Id: GenericListBlockParser.html 905940 2014-04-12 16:27:29Z hboutemy $
408     */
409    class TreeComponent
410    {
411        /**
412         * childrens
413         */
414        private List<TreeComponent> children = new ArrayList<TreeComponent>();
415
416        /**
417         * node text
418         */
419        private String text;
420
421        /**
422         * the father
423         */
424        private TreeComponent father;
425
426        /**
427         * type of the list
428         */
429        private GenericListBlockParser.Type type;
430
431        /**
432         * Creates the TreeComponent.
433         *
434         * @param father Component father
435         * @param text   component text
436         * @param type   component type
437         */
438        TreeComponent( final TreeComponent father, final String text, final GenericListBlockParser.Type type )
439        {
440            this.text = text;
441            this.father = father;
442            this.type = type;
443        }
444
445        /**
446         * @return my childrens
447         */
448        TreeComponent[] getChildren()
449        {
450            return (TreeComponent[]) children.toArray( new TreeComponent[] {} );
451        }
452
453        /**
454         * adds a children node
455         *
456         * @param t     text of the children
457         * @param ttype component type
458         * @return the new node created
459         */
460        TreeComponent addChildren( final String t, final GenericListBlockParser.Type ttype )
461        {
462            if ( t == null || ttype == null )
463            {
464                throw new IllegalArgumentException( "argument is null" );
465            }
466            final TreeComponent ret = new TreeComponent( this, t, ttype );
467            children.add( ret );
468
469            return ret;
470        }
471
472        /**
473         * @return the father
474         */
475        TreeComponent getFather()
476        {
477            return father;
478        }
479
480        /**
481         * @return the node depth in the tree
482         */
483        int getDepth()
484        {
485            int ret = 0;
486
487            TreeComponent c = this;
488
489            while ( ( c = c.getFather() ) != null )
490            {
491                ret++;
492            }
493
494            return ret;
495        }
496
497        /** {@inheritDoc} */
498        public String toString()
499        {
500            return toString( "" );
501        }
502
503        /** {@inheritDoc} */
504        public String toString( final String indent )
505        {
506            final StringBuilder sb = new StringBuilder();
507
508            if ( father != null )
509            {
510                sb.append( indent );
511                sb.append( "- " );
512                sb.append( text );
513                sb.append( GenericListBlockParser.EOL );
514            }
515            for ( TreeComponent lc : children )
516            {
517                sb.append( lc.toString( indent + "   " ) );
518            }
519            return sb.toString();
520        }
521
522        /**
523         * Returns the text.
524         *
525         * @return <code>String</code> with the text.
526         */
527        String getText()
528        {
529            return text;
530        }
531
532        /**
533         * Returns the type.
534         *
535         * @return <code>Type</code> with the text.
536         */
537        GenericListBlockParser.Type getType()
538        {
539            return type;
540        }
541    }
542}