001package org.apache.maven.doxia.module.confluence.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 java.util.ArrayList; 023import java.util.List; 024 025import org.codehaus.plexus.util.StringUtils; 026 027/** 028 * Re-usable builder that can be used to generate paragraph and list item text from a string containing all the content 029 * and wiki formatting. This class is intentionally stateful, but cheap to create, so create one as needed and keep it 030 * on the stack to preserve stateless behaviour in the caller. 031 * 032 * @author Dave Syer 033 * @version $Id: ChildBlocksBuilder.html 979316 2016-02-02 21:51:43Z hboutemy $ 034 * @since 1.1 035 */ 036public class ChildBlocksBuilder 037{ 038 private boolean insideBold = false; 039 040 private boolean insideItalic = false; 041 042 private boolean insideLink = false; 043 044 private boolean insideLinethrough = false; 045 046 private boolean insideUnderline = false; 047 048 private boolean insideSub = false; 049 050 private boolean insideSup = false; 051 052 private List<Block> blocks = new ArrayList<Block>(); 053 054 private StringBuilder text = new StringBuilder(); 055 056 private String input; 057 058 private boolean insideMonospaced; 059 060 /** 061 * <p>Constructor for ChildBlocksBuilder.</p> 062 * 063 * @param input the input. 064 */ 065 public ChildBlocksBuilder( String input ) 066 { 067 this.input = input; 068 } 069 070 /** 071 * Utility method to convert marked up content into blocks for rendering. 072 * 073 * @return a list of Blocks that can be used to render it 074 */ 075 public List<Block> getBlocks() 076 { 077 List<Block> specialBlocks = new ArrayList<Block>(); 078 079 for ( int i = 0; i < input.length(); i++ ) 080 { 081 char c = input.charAt( i ); 082 083 switch ( c ) 084 { 085 case '*': 086 if ( insideBold ) 087 { 088 insideBold = false; 089 specialBlocks = getList( new BoldBlock( getChildren( text, specialBlocks ) ), specialBlocks ); 090 text = new StringBuilder(); 091 } 092 else if ( insideMonospaced ) 093 { 094 text.append( c ); 095 } 096 else 097 { 098 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 099 insideBold = true; 100 } 101 102 break; 103 case '_': 104 if ( insideItalic ) 105 { 106 insideItalic = false; 107 specialBlocks = getList( new ItalicBlock( getChildren( text, specialBlocks ) ), specialBlocks ); 108 text = new StringBuilder(); 109 } 110 else if ( insideLink || insideMonospaced ) 111 { 112 text.append( c ); 113 } 114 else 115 { 116 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 117 insideItalic = true; 118 } 119 120 break; 121 case '-': 122 if ( insideLinethrough ) 123 { 124 insideLinethrough = false; 125 blocks.add( new LinethroughBlock( text.toString() ) ); 126 text = new StringBuilder(); 127 } 128 else if ( insideLink || insideMonospaced ) 129 { 130 text.append( c ); 131 } 132 else 133 { 134 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 135 insideLinethrough = true; 136 } 137 break; 138 case '+': 139 if ( insideUnderline ) 140 { 141 insideUnderline = false; 142 blocks.add( new UnderlineBlock( text.toString() ) ); 143 text = new StringBuilder(); 144 } 145 else if ( insideLink || insideMonospaced ) 146 { 147 text.append( c ); 148 } 149 else 150 { 151 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 152 insideUnderline = true; 153 } 154 break; 155 case '~': 156 if ( insideSub ) 157 { 158 insideSub = false; 159 blocks.add( new SubBlock( text.toString() ) ); 160 text = new StringBuilder(); 161 } 162 else if ( insideLink || insideMonospaced ) 163 { 164 text.append( c ); 165 } 166 else 167 { 168 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 169 insideSub = true; 170 } 171 break; 172 case '^': 173 if ( insideSup ) 174 { 175 insideSup = false; 176 blocks.add( new SupBlock( text.toString() ) ); 177 text = new StringBuilder(); 178 } 179 else if ( insideLink || insideMonospaced ) 180 { 181 text.append( c ); 182 } 183 else 184 { 185 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 186 insideSup = true; 187 } 188 break; 189 case '[': 190 if ( insideMonospaced ) 191 { 192 text.append( c ); 193 } 194 else 195 { 196 insideLink = true; 197 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 198 } 199 break; 200 case ']': 201 if ( insideLink ) 202 { 203 boolean addHTMLSuffix = false; 204 String link = text.toString(); 205 206 if ( !link.endsWith( ".html" ) ) 207 { 208 if ( !link.contains( "http" ) ) 209 { 210 // relative path: see DOXIA-298 211 addHTMLSuffix = true; 212 } 213 } 214 if ( link.contains( "|" ) ) 215 { 216 String[] pieces = StringUtils.split( text.toString(), "|" ); 217 218 if ( pieces[1].startsWith( "^" ) ) 219 { 220 // use the "file attachment" ^ syntax to force verbatim link: needed to allow actually 221 // linking to some non-html resources 222 pieces[1] = pieces[1].substring( 1 ); // now just get rid of the lead ^ 223 addHTMLSuffix = false; // force verbatim link to support attaching files/resources (not 224 // just .html files) 225 } 226 227 if ( addHTMLSuffix ) 228 { 229 if ( !pieces[1].contains( "#" ) ) 230 { 231 pieces[1] = pieces[1].concat( ".html" ); 232 } 233 else 234 { 235 if ( !pieces[1].startsWith( "#" ) ) 236 { 237 String[] temp = pieces[1].split( "#" ); 238 pieces[1] = temp[0] + ".html#" + temp[1]; 239 } 240 } 241 } 242 243 blocks.add( new LinkBlock( pieces[1], pieces[0] ) ); 244 } 245 else 246 { 247 String value = link; 248 249 if ( link.startsWith( "#" ) ) 250 { 251 value = link.substring( 1 ); 252 } 253 else if ( link.startsWith( "^" ) ) 254 { 255 link = link.substring( 1 ); // chop off the lead ^ from link and from value 256 value = link; 257 addHTMLSuffix = false; // force verbatim link to support attaching files/resources (not 258 // just .html files) 259 } 260 261 if ( addHTMLSuffix ) 262 { 263 if ( !link.contains( "#" ) ) 264 { 265 link = link.concat( ".html" ); 266 } 267 else 268 { 269 if ( !link.startsWith( "#" ) ) 270 { 271 String[] temp = link.split( "#" ); 272 link = temp[0] + ".html#" + temp[1]; 273 } 274 } 275 } 276 277 blocks.add( new LinkBlock( link, value ) ); 278 } 279 280 text = new StringBuilder(); 281 insideLink = false; 282 } 283 else if ( insideMonospaced ) 284 { 285 text.append( c ); 286 } 287 288 break; 289 case '{': 290 if ( insideMonospaced ) 291 { 292 text.append( c ); 293 } 294 else 295 { 296 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 297 298 if ( nextChar( input, i ) == '{' ) // it's monospaced 299 { 300 i++; 301 insideMonospaced = true; 302 } 303 } 304 // else it's a confluence macro... 305 306 break; 307 case '}': 308 if ( nextChar( input, i ) == '}' ) 309 { 310 i++; 311 insideMonospaced = false; 312 specialBlocks = getList( new MonospaceBlock( getChildren( text, specialBlocks ) ), 313 specialBlocks ); 314 text = new StringBuilder(); 315 } 316 else if ( insideMonospaced ) 317 { 318 text.append( c ); 319 } 320 else 321 { 322 String name = text.toString(); 323 if ( name.startsWith( "anchor:" ) ) 324 { 325 blocks.add( new AnchorBlock( name.substring( "anchor:".length() ) ) ); 326 } 327 else 328 { 329 blocks.add( new TextBlock( "{" + name + "}" ) ); 330 } 331 text = new StringBuilder(); 332 } 333 334 break; 335 case '\\': 336 if ( insideMonospaced ) 337 { 338 text.append( c ); 339 } 340 else if ( nextChar( input, i ) == '\\' ) 341 { 342 i++; 343 text = addTextBlockIfNecessary( blocks, specialBlocks, text ); 344 blocks.add( new LinebreakBlock() ); 345 } 346 else 347 { 348 // DOXIA-467 single trailing backward slash, double is considered linebreak 349 if ( i == input.length() - 1 ) 350 { 351 text.append( '\\' ); 352 } 353 else 354 { 355 text.append( input.charAt( ++i ) ); 356 } 357 } 358 359 break; 360 default: 361 text.append( c ); 362 } 363 364 if ( !specialBlocks.isEmpty() ) 365 { 366 if ( !insideItalic && !insideBold && !insideMonospaced ) 367 { 368 blocks.addAll( specialBlocks ); 369 specialBlocks.clear(); 370 } 371 } 372 373 } 374 375 if ( text.length() > 0 ) 376 { 377 blocks.add( new TextBlock( text.toString() ) ); 378 } 379 380 return blocks; 381 } 382 383 private List<Block> getList( Block block, List<Block> currentBlocks ) 384 { 385 List<Block> list = new ArrayList<Block>(); 386 387 if ( insideBold || insideItalic || insideMonospaced ) 388 { 389 list.addAll( currentBlocks ); 390 } 391 392 list.add( block ); 393 394 return list; 395 } 396 397 private List<Block> getChildren( StringBuilder buffer, List<Block> currentBlocks ) 398 { 399 String txt = buffer.toString().trim(); 400 401 if ( currentBlocks.isEmpty() && StringUtils.isEmpty( txt ) ) 402 { 403 return new ArrayList<Block>(); 404 } 405 406 ArrayList<Block> list = new ArrayList<Block>(); 407 408 if ( !insideBold && !insideItalic && !insideMonospaced ) 409 { 410 list.addAll( currentBlocks ); 411 } 412 413 if ( StringUtils.isEmpty( txt ) ) 414 { 415 return list; 416 } 417 418 list.add( new TextBlock( txt ) ); 419 420 return list; 421 } 422 423 private static char nextChar( String input, int i ) 424 { 425 return input.length() > i + 1 ? input.charAt( i + 1 ) : '\0'; 426 } 427 428 private StringBuilder addTextBlockIfNecessary( List<Block> blcks, List<Block> specialBlocks, StringBuilder txt ) 429 { 430 if ( txt.length() == 0 ) 431 { 432 return txt; 433 } 434 435 TextBlock textBlock = new TextBlock( txt.toString() ); 436 437 if ( !insideBold && !insideItalic && !insideMonospaced ) 438 { 439 blcks.add( textBlock ); 440 } 441 else 442 { 443 specialBlocks.add( textBlock ); 444 } 445 446 return new StringBuilder(); 447 } 448 449}