View Javadoc
1   package org.apache.maven.doxia.module.twiki.parser;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.doxia.util.ByLineSource;
23  import org.apache.maven.doxia.parser.ParseException;
24  import org.apache.maven.doxia.sink.Sink;
25  
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  /**
32   * Generic list parser
33   *
34   * @author Juan F. Codagnone
35   */
36  public class GenericListBlockParser
37      implements BlockParser
38  {
39      static final String EOL = System.getProperty( "line.separator" );
40  
41      /**
42       * parser used to create text blocks
43       */
44      private FormatedTextParser formatedTextParser;
45  
46      /**
47       * supported patterns
48       */
49      private final Pattern[] patterns = new Pattern[TYPES.length];
50  
51      /**
52       * Creates the GenericListBlockParser.
53       */
54      public GenericListBlockParser()
55      {
56          for ( int i = 0; i < TYPES.length; i++ )
57          {
58              patterns[i] = Pattern.compile( "^((   )+)" + TYPES[i].getItemPattern() + "(.*)$" );
59          }
60      }
61  
62      /** {@inheritDoc} */
63      public final boolean accept( final String line )
64      {
65          boolean ret = false;
66  
67          for ( int i = 0; !ret && i < patterns.length; i++ )
68          {
69              ret |= patterns[i].matcher( line ).lookingAt();
70          }
71  
72          return ret;
73      }
74  
75      /** {@inheritDoc} */
76      public final Block visit( final String line, final ByLineSource source )
77          throws ParseException
78      {
79          final TreeListBuilder treeListBuilder = new TreeListBuilder( formatedTextParser );
80          // new TreeListBuilder(formatedTextParser);
81          String l = line;
82          do
83          {
84              if ( !accept( l ) )
85              {
86                  break;
87              }
88  
89              for ( int i = 0; i < patterns.length; i++ )
90              {
91                  final Matcher m = patterns[i].matcher( l );
92                  if ( m.lookingAt() )
93                  {
94                      final int numberOfSpaces = 3;
95                      final int textGroup = 3;
96                      assert m.group( 1 ).length() % numberOfSpaces == 0;
97                      final int level = m.group( 1 ).length() / numberOfSpaces;
98                      treeListBuilder.feedEntry( TYPES[i], level, m.group( textGroup ).trim() );
99                      break;
100                 }
101             }
102         }
103         while ( ( l = source.getNextLine() ) != null );
104 
105         if ( l != null )
106         {
107             source.ungetLine();
108         }
109 
110         return treeListBuilder.getBlock();
111     }
112 
113     /**
114      * Sets the formatTextParser.
115      *
116      * @param textParser <code>FormatedTextParser</code> with the formatTextParser.
117      */
118     public final void setTextParser( final FormatedTextParser textParser )
119     {
120         if ( textParser == null )
121         {
122             throw new IllegalArgumentException( "formatTextParser can't be null" );
123         }
124         this.formatedTextParser = textParser;
125     }
126 
127     interface Type
128     {
129         /**
130          * @return the pattern of the item part of the list regex
131          */
132         String getItemPattern();
133 
134         /**
135          * @param items children of the new listblock
136          * @return a new ListBlock
137          */
138         ListBlock createList( final ListItemBlock[] items );
139 
140     }
141 
142     /**
143      * unordered list
144      */
145     private static final Type LIST = new Type()
146     {
147         /** {@inheritDoc} */
148         public String getItemPattern()
149         {
150             return "[*]";
151         }
152 
153         /** {@inheritDoc} */
154         public ListBlock createList( final ListItemBlock[] items )
155         {
156             return new UnorderedListBlock( items );
157         }
158     };
159 
160     /**
161      * a.
162      */
163     private static final Type ORDERED_LOWER_ALPHA = new Type()
164     {
165         /** {@inheritDoc} */
166         public String getItemPattern()
167         {
168             return "[a-hj-z][.]";
169         }
170 
171         /** {@inheritDoc} */
172         public ListBlock createList( final ListItemBlock[] items )
173         {
174             return new NumeratedListBlock( Sink.NUMBERING_LOWER_ALPHA, items );
175         }
176     };
177 
178     /**
179      * A.
180      */
181     private static final Type ORDERED_UPPER_ALPHA = new Type()
182     {
183         /** {@inheritDoc} */
184         public String getItemPattern()
185         {
186             return "[A-HJ-Z][.]";
187         }
188 
189         /** {@inheritDoc} */
190         public ListBlock createList( final ListItemBlock[] items )
191         {
192             return new NumeratedListBlock( Sink.NUMBERING_UPPER_ALPHA, items );
193         }
194     };
195 
196     /**
197      * 1.
198      */
199     private static final Type ORDERERED_DECIMAL = new Type()
200     {
201         /** {@inheritDoc} */
202         public String getItemPattern()
203         {
204             return "[0123456789][.]";
205         }
206 
207         /** {@inheritDoc} */
208         public ListBlock createList( final ListItemBlock[] items )
209         {
210             return new NumeratedListBlock( Sink.NUMBERING_DECIMAL, items );
211         }
212     };
213 
214     /**
215      * i.
216      */
217     private static final Type ORDERERED_LOWER_ROMAN = new Type()
218     {
219         /** {@inheritDoc} */
220         public String getItemPattern()
221         {
222             return "[i][.]";
223         }
224 
225         /** {@inheritDoc} */
226         public ListBlock createList( final ListItemBlock[] items )
227         {
228             return new NumeratedListBlock( Sink.NUMBERING_LOWER_ROMAN, items );
229         }
230     };
231 
232     /**
233      * I.
234      */
235     private static final Type ORDERERED_UPPER_ROMAN = new Type()
236     {
237         /** {@inheritDoc} */
238         public String getItemPattern()
239         {
240             return "[I][.]";
241         }
242 
243         /** {@inheritDoc} */
244         public ListBlock createList( final ListItemBlock[] items )
245         {
246             return new NumeratedListBlock( Sink.NUMBERING_UPPER_ROMAN, items );
247         }
248     };
249 
250     private static final Type[] TYPES =
251         { LIST, ORDERED_LOWER_ALPHA, ORDERED_UPPER_ALPHA, ORDERERED_DECIMAL, ORDERERED_LOWER_ROMAN,
252             ORDERERED_UPPER_ROMAN };
253 
254 }
255 
256 /**
257  * It helps to build
258  *
259  * @author Juan F. Codagnone
260  */
261 class TreeListBuilder
262 {
263     /**
264      * parser that create text blocks
265      */
266     private final FormatedTextParser textParser;
267 
268     /**
269      * tree root
270      */
271     private final TreeComponent root;
272 
273     /**
274      * the current element of the tree
275      */
276     private TreeComponent current;
277 
278     /**
279      * Creates the TreeListBuilder.
280      *
281      * @param formatTextParser parser that create text blocks
282      * @throws IllegalArgumentException if <code>formatTextParser</code> is null
283      */
284     TreeListBuilder( final FormatedTextParser formatTextParser )
285         throws IllegalArgumentException
286     {
287         if ( formatTextParser == null )
288         {
289             throw new IllegalArgumentException( "argument is null" );
290         }
291         this.textParser = formatTextParser;
292         root = new TreeComponent( null, "root", null );
293         current = root;
294     }
295 
296     /**
297      * recibe un nivel y un texto y armar magicamente (manteniendo estado)
298      * el �rbol
299      *
300      * @param type  type of list
301      * @param level indentation level of the item
302      * @param text  text of the item
303      */
304     void feedEntry( final GenericListBlockParser.Type type, final int level, final String text )
305     {
306         final int currentDepth = current.getDepth();
307         final int incomingLevel = level - 1;
308 
309         if ( incomingLevel == currentDepth )
310         {
311             // nothing to move
312         }
313         else if ( incomingLevel > currentDepth )
314         {
315             // el actual ahora es el �ltimo que insert�
316             final TreeComponent[] components = current.getChildren();
317             if ( components.length == 0 )
318             {
319                 /* for example:
320                  *        * item1
321                  *     * item2
322                  */
323                 for ( int i = 0, n = incomingLevel - currentDepth; i < n; i++ )
324                 {
325                     current = current.addChildren( "", type );
326                 }
327             }
328             else
329             {
330                 current = components[components.length - 1];
331             }
332 
333         }
334         else
335         {
336             for ( int i = 0, n = currentDepth - incomingLevel; i < n; i++ )
337             {
338                 current = current.getFather();
339                 if ( current == null )
340                 {
341                     throw new IllegalStateException();
342                 }
343             }
344         }
345         current.addChildren( text, type );
346     }
347 
348     /**
349      * @return a Block for the list that we received
350      */
351     ListBlock getBlock()
352     {
353         return getList( root );
354     }
355 
356     /**
357      * Wrapper
358      *
359      * @param tc tree
360      * @return list Block for this tree
361      */
362     private ListBlock getList( final TreeComponent tc )
363     {
364         ListItemBlock[] li = getListItems( tc ).toArray( new ListItemBlock[] {} );
365         return tc.getChildren()[0].getType().createList( li );
366     }
367 
368     /**
369      * @param tc tree
370      * @return list Block for this tree
371      */
372     private List<ListItemBlock> getListItems( final TreeComponent tc )
373     {
374         final List<ListItemBlock> blocks = new ArrayList<>();
375 
376         for ( int i = 0; i < tc.getChildren().length; i++ )
377         {
378             final TreeComponent child = tc.getChildren()[i];
379 
380             Block[] text = new Block[] {};
381             if ( child.getFather() != null )
382             {
383                 text = textParser.parse( child.getText() );
384             }
385 
386             if ( child.getChildren().length != 0 )
387             {
388                 blocks.add( new ListItemBlock( text, getList( child ) ) );
389             }
390             else
391             {
392                 blocks.add( new ListItemBlock( text ) );
393             }
394         }
395 
396         return blocks;
397     }
398 
399     /**
400      * A bidirectional tree node
401      *
402      * @author Juan F. Codagnone
403      */
404     static class TreeComponent
405     {
406         /**
407          * childrens
408          */
409         private List<TreeComponent> children = new ArrayList<>();
410 
411         /**
412          * node text
413          */
414         private String text;
415 
416         /**
417          * the father
418          */
419         private TreeComponent father;
420 
421         /**
422          * type of the list
423          */
424         private GenericListBlockParser.Type type;
425 
426         /**
427          * Creates the TreeComponent.
428          *
429          * @param father Component father
430          * @param text   component text
431          * @param type   component type
432          */
433         TreeComponent( final TreeComponent father, final String text, final GenericListBlockParser.Type type )
434         {
435             this.text = text;
436             this.father = father;
437             this.type = type;
438         }
439 
440         /**
441          * @return my childrens
442          */
443         TreeComponent[] getChildren()
444         {
445             return children.toArray( new TreeComponent[] {} );
446         }
447 
448         /**
449          * adds a children node
450          *
451          * @param t     text of the children
452          * @param ttype component type
453          * @return the new node created
454          */
455         TreeComponent addChildren( final String t, final GenericListBlockParser.Type ttype )
456         {
457             if ( t == null || ttype == null )
458             {
459                 throw new IllegalArgumentException( "argument is null" );
460             }
461             final TreeComponent ret = new TreeComponent( this, t, ttype );
462             children.add( ret );
463 
464             return ret;
465         }
466 
467         /**
468          * @return the father
469          */
470         TreeComponent getFather()
471         {
472             return father;
473         }
474 
475         /**
476          * @return the node depth in the tree
477          */
478         int getDepth()
479         {
480             int ret = 0;
481 
482             TreeComponent c = this;
483 
484             while ( ( c = c.getFather() ) != null )
485             {
486                 ret++;
487             }
488 
489             return ret;
490         }
491 
492         /** {@inheritDoc} */
493         public String toString()
494         {
495             return toString( "" );
496         }
497 
498         /** {@inheritDoc} */
499         public String toString( final String indent )
500         {
501             final StringBuilder sb = new StringBuilder();
502 
503             if ( father != null )
504             {
505                 sb.append( indent );
506                 sb.append( "- " );
507                 sb.append( text );
508                 sb.append( GenericListBlockParser.EOL );
509             }
510             for ( TreeComponent lc : children )
511             {
512                 sb.append( lc.toString( indent + "   " ) );
513             }
514             return sb.toString();
515         }
516 
517         /**
518          * Returns the text.
519          *
520          * @return <code>String</code> with the text.
521          */
522         String getText()
523         {
524             return text;
525         }
526 
527         /**
528          * Returns the type.
529          *
530          * @return <code>Type</code> with the text.
531          */
532         GenericListBlockParser.Type getType()
533         {
534             return type;
535         }
536     }
537 }