View Javadoc
1   package org.apache.maven.doxia.module.confluence.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 java.util.ArrayList;
23  import java.util.List;
24  
25  import org.codehaus.plexus.util.StringUtils;
26  
27  /**
28   * Re-usable builder that can be used to generate paragraph and list item text from a string containing all the content
29   * and wiki formatting. This class is intentionally stateful, but cheap to create, so create one as needed and keep it
30   * on the stack to preserve stateless behaviour in the caller.
31   *
32   * @author Dave Syer
33   * @version $Id: ChildBlocksBuilder.java 1542771 2013-11-17 17:50:31Z hboutemy $
34   * @since 1.1
35   */
36  public class ChildBlocksBuilder
37  {
38      private boolean insideBold = false;
39  
40      private boolean insideItalic = false;
41  
42      private boolean insideLink = false;
43  
44      private boolean insideLinethrough = false;
45  
46      private boolean insideUnderline = false;
47  
48      private boolean insideSub = false;
49  
50      private boolean insideSup = false;
51  
52      private List<Block> blocks = new ArrayList<Block>();
53  
54      private StringBuilder text = new StringBuilder();
55  
56      private String input;
57  
58      private boolean insideMonospaced;
59  
60      /**
61       * <p>Constructor for ChildBlocksBuilder.</p>
62       *
63       * @param input the input.
64       */
65      public ChildBlocksBuilder( String input )
66      {
67          this.input = input;
68      }
69  
70      /**
71       * Utility method to convert marked up content into blocks for rendering.
72       *
73       * @return a list of Blocks that can be used to render it
74       */
75      public List<Block> getBlocks()
76      {
77          List<Block> specialBlocks = new ArrayList<Block>();
78  
79          for ( int i = 0; i < input.length(); i++ )
80          {
81              char c = input.charAt( i );
82  
83              switch ( c )
84              {
85                  case '*':
86                      if ( insideBold )
87                      {
88                          insideBold = false;
89                          specialBlocks = getList( new BoldBlock( getChildren( text, specialBlocks ) ), specialBlocks );
90                          text = new StringBuilder();
91                      }
92                      else
93                      {
94                          text = addTextBlockIfNecessary( blocks, specialBlocks, text );
95                          insideBold = true;
96                      }
97  
98                      break;
99                  case '_':
100                     if ( insideItalic )
101                     {
102                         insideItalic = false;
103                         specialBlocks = getList( new ItalicBlock( getChildren( text, specialBlocks ) ), specialBlocks );
104                         text = new StringBuilder();
105                     }
106                     else if ( insideLink )
107                     {
108                         text.append( '_' );    
109                     }
110                     else
111                     {
112                         text = addTextBlockIfNecessary( blocks, specialBlocks, text );
113                         insideItalic = true;
114                     }
115 
116                     break;
117                 case '-':
118                     if ( insideLinethrough )
119                     {
120                         insideLinethrough = false;
121                         blocks.add( new LinethroughBlock( text.toString() ) );
122                         text = new StringBuilder();
123                     }
124                     else if ( insideLink )
125                     {
126                         text.append( c );    
127                     }
128                     else
129                     {
130                         text = addTextBlockIfNecessary( blocks, specialBlocks, text );
131                         insideLinethrough = true;                            
132                     }
133                     break;
134                 case '+':
135                     if ( insideUnderline )
136                     {
137                         insideUnderline = false;
138                         blocks.add( new UnderlineBlock( text.toString() ) );
139                         text = new StringBuilder();
140                     }
141                     else if ( insideLink )
142                     {
143                         text.append( c );    
144                     }
145                     else
146                     {
147                         text = addTextBlockIfNecessary( blocks, specialBlocks, text );
148                         insideUnderline = true;                            
149                     }
150                     break;
151                 case '~':
152                     if ( insideSub )
153                     {
154                         insideSub = false;
155                         blocks.add( new SubBlock( text.toString() ) );
156                         text = new StringBuilder();
157                     }
158                     else if ( insideLink )
159                     {
160                         text.append( c );    
161                     }
162                     else
163                     {
164                         text = addTextBlockIfNecessary( blocks, specialBlocks, text );
165                         insideSub = true;                            
166                     }
167                     break;
168                 case '^':
169                     if ( insideSup )
170                     {
171                         insideSup = false;
172                         blocks.add( new SupBlock( text.toString() ) );
173                         text = new StringBuilder();
174                     }
175                     else if ( insideLink )
176                     {
177                         text.append( c );    
178                     }
179                     else
180                     {
181                         text = addTextBlockIfNecessary( blocks, specialBlocks, text );
182                         insideSup = true;                            
183                     }
184                     break;
185                 case '[':
186                     insideLink = true;
187                     text = addTextBlockIfNecessary( blocks, specialBlocks, text );
188                     break;
189                 case ']':
190                     if ( insideLink )
191                     {
192                         boolean addHTMLSuffix = false;
193                         String link = text.toString();
194 
195                         if ( !link.endsWith( ".html" ) )
196                         {
197                             if ( !link.contains( "http" ) )
198                             {
199                                 // relative path: see DOXIA-298
200                                 addHTMLSuffix = true;
201                             }
202                         }
203                         if ( link.contains( "|" ) )
204                         {
205                             String[] pieces = StringUtils.split( text.toString(), "|" );
206                             
207                             if ( pieces[1].startsWith( "^" ) )
208                             {
209                                 // use the "file attachment" ^ syntax to force verbatim link: needed to allow actually
210                                 // linking to some non-html resources
211                                 pieces[1] = pieces[1].substring( 1 ); // now just get rid of the lead ^
212                                 addHTMLSuffix = false; // force verbatim link to support attaching files/resources (not
213                                                        // just .html files)
214                             }
215 
216                             if ( addHTMLSuffix )
217                             {
218                                 if ( !pieces[1].contains( "#" ) )
219                                 {
220                                     pieces[1] = pieces[1].concat( ".html" );
221                                 }
222                                 else
223                                 {
224                                     if ( !pieces[1].startsWith( "#" ) )
225                                     {
226                                         String[] temp = pieces[1].split( "#" );
227                                         pieces[1] = temp[0] + ".html#" + temp[1];
228                                     }
229                                 }
230                             }
231 
232                             blocks.add( new LinkBlock( pieces[1], pieces[0] ) );
233                         }
234                         else
235                         {
236                             String value = link;
237 
238                             if ( link.startsWith( "#" ) )
239                             {
240                                 value = link.substring( 1 );
241                             }
242                             else if ( link.startsWith( "^" ) )
243                             {
244                                 link = link.substring( 1 );  // chop off the lead ^ from link and from value
245                                 value = link;
246                                 addHTMLSuffix =
247                                     false; // force verbatim link to support attaching files/resources (not just .html files)
248                             }
249 
250                             if ( addHTMLSuffix )
251                             {
252                                 if ( !link.contains( "#" ) )
253                                 {
254                                     link = link.concat( ".html" );
255                                 }
256                                 else
257                                 {
258                                     if ( !link.startsWith( "#" ) )
259                                     {
260                                         String[] temp = link.split( "#" );
261                                         link = temp[0] + ".html#" + temp[1];
262                                     }
263                                 }
264                             }
265 
266                             blocks.add( new LinkBlock( link, value ) );
267                         }
268 
269                         text = new StringBuilder();
270                         insideLink = false;
271                     }
272 
273                     break;
274                 case '{':
275 
276                     text = addTextBlockIfNecessary( blocks, specialBlocks, text );
277 
278                     if ( nextChar( input, i ) == '{' ) // it's monospaced
279                     {
280                         i++;
281                         insideMonospaced = true;
282                     }
283                     // else it's a confluence macro...
284 
285                     break;
286                 case '}':
287 
288                     // System.out.println( "line = " + line );
289 
290                     if ( nextChar( input, i ) == '}' )
291                     {
292                         i++;
293                         insideMonospaced = false;
294                         specialBlocks = getList( new MonospaceBlock( getChildren( text, specialBlocks ) ),
295                                                  specialBlocks );
296                         text = new StringBuilder();
297                     }
298                     else
299                     {
300                         String name = text.toString();
301                         if ( name.startsWith( "anchor:" ) )
302                         {
303                             blocks.add( new AnchorBlock( name.substring( "anchor:".length() ) ) );
304                         }
305                         else
306                         {
307                             blocks.add( new TextBlock( "{" + name + "}" ) );
308                         }
309                         text = new StringBuilder();
310                     }
311 
312                     break;
313                 case '\\':
314                     if ( insideMonospaced )
315                     {
316                         text.append( c );
317                     }
318                     else if ( nextChar( input, i ) == '\\' )
319                     {
320                         i++;
321                         text = addTextBlockIfNecessary( blocks, specialBlocks, text );
322                         blocks.add( new LinebreakBlock() );
323                     }
324                     else
325                     {
326                         // DOXIA-467 single trailing backward slash, double is considered linebreak
327                         if ( i == input.length() - 1 )
328                         {
329                             text.append( '\\' );
330                         }
331                         else
332                         {
333                             text.append( input.charAt( ++i ) );
334                         }
335                     }
336 
337                     break;
338                 default:
339                     text.append( c );
340             }
341 
342             if ( !specialBlocks.isEmpty() )
343             {
344                 if ( !insideItalic && !insideBold && !insideMonospaced )
345                 {
346                     blocks.addAll( specialBlocks );
347                     specialBlocks.clear();
348                 }
349             }
350 
351         }
352 
353         if ( text.length() > 0 )
354         {
355             blocks.add( new TextBlock( text.toString() ) );
356         }
357 
358         return blocks;
359     }
360 
361     private List<Block> getList( Block block, List<Block> currentBlocks )
362     {
363         List<Block> list = new ArrayList<Block>();
364 
365         if ( insideBold || insideItalic || insideMonospaced )
366         {
367             list.addAll( currentBlocks );
368         }
369 
370         list.add( block );
371 
372         return list;
373     }
374 
375     private List<Block> getChildren( StringBuilder buffer, List<Block> currentBlocks )
376     {
377         String txt = buffer.toString().trim();
378 
379         if ( currentBlocks.isEmpty() && StringUtils.isEmpty( txt ) )
380         {
381             return new ArrayList<Block>();
382         }
383 
384         ArrayList<Block> list = new ArrayList<Block>();
385 
386         if ( !insideBold && !insideItalic && !insideMonospaced )
387         {
388             list.addAll( currentBlocks );
389         }
390 
391         if ( StringUtils.isEmpty( txt ) )
392         {
393             return list;
394         }
395 
396         list.add( new TextBlock( txt ) );
397 
398         return list;
399     }
400 
401     private static char nextChar( String input, int i )
402     {
403         return input.length() > i + 1 ? input.charAt( i + 1 ) : '\0';
404     }
405 
406     private StringBuilder addTextBlockIfNecessary( List<Block> blcks, List<Block> specialBlocks, StringBuilder txt )
407     {
408         if ( txt.length() == 0 )
409         {
410             return txt;
411         }
412 
413         TextBlock textBlock = new TextBlock( txt.toString() );
414 
415         if ( !insideBold && !insideItalic && !insideMonospaced )
416         {
417             blcks.add( textBlock );
418         }
419         else
420         {
421             specialBlocks.add( textBlock );
422         }
423 
424         return new StringBuilder();
425     }
426 
427 }