001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.htmltowiki; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.PrintWriter; 024import java.io.UnsupportedEncodingException; 025import java.net.URLDecoder; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.LinkedHashMap; 029import java.util.Map; 030 031import org.apache.commons.lang.StringEscapeUtils; 032import org.jdom2.Attribute; 033import org.jdom2.Content; 034import org.jdom2.Element; 035import org.jdom2.JDOMException; 036import org.jdom2.Text; 037import org.jdom2.xpath.XPath; 038 039/** 040 * Converting XHtml to Wiki Markup. This is the class which does all of the heavy loading. 041 * 042 */ 043public class XHtmlElementToWikiTranslator 044{ 045 private static final String UTF8 = "UTF-8"; 046 047 private XHtmlToWikiConfig m_config; 048 049 private WhitespaceTrimWriter m_outTimmer; 050 051 private PrintWriter m_out; 052 053 private LiStack m_liStack = new LiStack(); 054 055 private PreStack m_preStack = new PreStack(); 056 057 /** 058 * Create a new translator using the default config. 059 * 060 * @param base The base element from which to start translating. 061 * @throws IOException If reading of the DOM tree fails. 062 * @throws JDOMException If the DOM tree is faulty. 063 */ 064 public XHtmlElementToWikiTranslator( Element base ) throws IOException, JDOMException 065 { 066 this( base, new XHtmlToWikiConfig() ); 067 } 068 069 /** 070 * Create a new translator using the specified config. 071 * 072 * @param base The base element from which to start translating. 073 * @param config The config to use. 074 * @throws IOException If reading of the DOM tree fails. 075 * @throws JDOMException If the DOM tree is faulty. 076 */ 077 public XHtmlElementToWikiTranslator( Element base, XHtmlToWikiConfig config ) throws IOException, JDOMException 078 { 079 this.m_config = config; 080 m_outTimmer = new WhitespaceTrimWriter(); 081 m_out = new PrintWriter( m_outTimmer ); 082 print( base ); 083 } 084 085 /** 086 * FIXME: I have no idea what this does... 087 * 088 * @return Something. 089 */ 090 public String getWikiString() 091 { 092 return m_outTimmer.toString(); 093 } 094 095 private void print( String s ) 096 { 097 s = StringEscapeUtils.unescapeHtml( s ); 098 m_out.print( s ); 099 } 100 101 private void print( Object element ) throws IOException, JDOMException 102 { 103 if( element instanceof Text ) 104 { 105 Text t = (Text)element; 106 String s = t.getText(); 107 if( m_preStack.isPreMode() ) 108 { 109 m_out.print( s ); 110 } 111 else 112 { 113 // remove all "line terminator" characters 114 s = s.replaceAll( "[\\r\\n\\f\\u0085\\u2028\\u2029]", "" ); 115 m_out.print( s ); 116 } 117 } 118 else if( element instanceof Element ) 119 { 120 Element base = (Element)element; 121 String n = base.getName().toLowerCase(); 122 if( "imageplugin".equals( base.getAttributeValue( "class" ) ) ) 123 { 124 printImage( base ); 125 } 126 else if( "wikiform".equals( base.getAttributeValue( "class" ) ) ) 127 { 128 // only print the children if the div's class="wikiform", but not the div itself. 129 printChildren( base ); 130 } 131 else 132 { 133 boolean bold = false; 134 boolean italic = false; 135 boolean monospace = false; 136 String cssSpecial = null; 137 String cssClass = base.getAttributeValue( "class" ); 138 139 // accomodate a FCKeditor bug with Firefox: when a link is removed, it becomes <span class="wikipage">text</span>. 140 boolean ignoredCssClass = cssClass != null && cssClass.matches( "wikipage|createpage|external|interwiki|attachment|inline-code" ); 141 142 Map styleProps = null; 143 144 // Only get the styles if it's not a link element. Styles for link elements are 145 // handled as an AugmentedWikiLink instead. 146 if( !n.equals( "a" ) ) 147 { 148 styleProps = getStylePropertiesLowerCase( base ); 149 } 150 151 if( cssClass != null && "inline-code".equals(cssClass) ) 152 { 153 monospace = true; 154 } 155 156 if( styleProps != null ) 157 { 158 String weight = (String)styleProps.remove( "font-weight" ); 159 String style = (String)styleProps.remove( "font-style" ); 160 161 if( n.equals( "p" ) ) 162 { 163 // change it so we can print out the css styles for <p> 164 n = "div"; 165 } 166 167 italic = "oblique".equals( style ) || "italic".equals( style ); 168 bold = "bold".equals( weight ) || "bolder".equals( weight ); 169 if( !styleProps.isEmpty() ) 170 { 171 cssSpecial = propsToStyleString( styleProps ); 172 } 173 } 174 if( cssClass != null && !ignoredCssClass ) 175 { 176 if( n.equals( "div" ) ) 177 { 178 m_out.print( "\n%%" + cssClass + " \n" ); 179 } 180 else if( n.equals( "span" ) ) 181 { 182 m_out.print( "%%" + cssClass + " " ); 183 } 184 } 185 if( bold ) 186 { 187 m_out.print( "__" ); 188 } 189 if( italic ) 190 { 191 m_out.print( "''" ); 192 } 193 if( monospace ) 194 { 195 m_out.print( "{{{" ); 196 m_preStack.push(); 197 } 198 if( cssSpecial != null ) 199 { 200 if( n.equals( "div" ) ) 201 { 202 m_out.print( "\n%%(" + cssSpecial + " )\n" ); 203 } 204 else 205 { 206 m_out.print( "%%(" + cssSpecial + " )" ); 207 } 208 } 209 printChildren( base ); 210 if( cssSpecial != null ) 211 { 212 if( n.equals( "div" ) ) 213 { 214 m_out.print( "\n/%\n" ); 215 } 216 else 217 { 218 m_out.print( "/%" ); 219 } 220 } 221 if( monospace ) 222 { 223 m_preStack.pop(); 224 m_out.print( "}}}" ); 225 } 226 if( italic ) 227 { 228 m_out.print( "''" ); 229 } 230 if( bold ) 231 { 232 m_out.print( "__" ); 233 } 234 if( cssClass != null && !ignoredCssClass ) 235 { 236 if( n.equals( "div" ) ) 237 { 238 m_out.print( "\n/%\n" ); 239 } 240 else if( n.equals( "span" ) ) 241 { 242 m_out.print( "/%" ); 243 } 244 } 245 } 246 } 247 } 248 249 private void printChildren( Element base ) throws IOException, JDOMException 250 { 251 for( Iterator< Content > i = base.getContent().iterator(); i.hasNext(); ) 252 { 253 Content c = i.next(); 254 if( c instanceof Element ) 255 { 256 Element e = (Element)c; 257 String n = e.getName().toLowerCase(); 258 if( n.equals( "h1" ) ) 259 { 260 m_out.print( "\n!!! " ); 261 print( e ); 262 m_out.println(); 263 } 264 else if( n.equals( "h2" ) ) 265 { 266 m_out.print( "\n!!! " ); 267 print( e ); 268 m_out.println(); 269 } 270 else if( n.equals( "h3" ) ) 271 { 272 m_out.print( "\n!! " ); 273 print( e ); 274 m_out.println(); 275 } 276 else if( n.equals( "h4" ) ) 277 { 278 m_out.print( "\n! " ); 279 print( e ); 280 m_out.println(); 281 } 282 else if( n.equals( "p" ) ) 283 { 284 if( e.getContentSize() != 0 ) // we don't want to print empty elements: <p></p> 285 { 286 m_out.println(); 287 print( e ); 288 m_out.println(); 289 } 290 } 291 else if( n.equals( "br" ) ) 292 { 293 if( m_preStack.isPreMode() ) 294 { 295 m_out.println(); 296 } 297 else 298 { 299 String parentElementName = base.getName().toLowerCase(); 300 301 // 302 // To beautify the generated wiki markup, we print a newline character after a linebreak. 303 // It's only safe to do this when the parent element is a <p> or <div>; when the parent 304 // element is a table cell or list item, a newline character would break the markup. 305 // We also check that this isn't being done inside a plugin body. 306 // 307 if( parentElementName.matches( "p|div" ) 308 && !base.getText().matches( "(?s).*\\[\\{.*\\}\\].*" ) ) 309 { 310 m_out.print( " \\\\\n" ); 311 } 312 else 313 { 314 m_out.print( " \\\\" ); 315 } 316 } 317 print( e ); 318 } 319 else if( n.equals( "hr" ) ) 320 { 321 m_out.println(); 322 print( "----" ); 323 print( e ); 324 m_out.println(); 325 } 326 else if( n.equals( "table" ) ) 327 { 328 if( !m_outTimmer.isCurrentlyOnLineBegin() ) 329 { 330 m_out.println(); 331 } 332 print( e ); 333 } 334 else if( n.equals( "tr" ) ) 335 { 336 print( e ); 337 m_out.println(); 338 } 339 else if( n.equals( "td" ) ) 340 { 341 m_out.print( "| " ); 342 print( e ); 343 if( !m_preStack.isPreMode() ) 344 { 345 print( " " ); 346 } 347 } 348 else if( n.equals( "th" ) ) 349 { 350 m_out.print( "|| " ); 351 print( e ); 352 if( !m_preStack.isPreMode() ) 353 { 354 print( " " ); 355 } 356 } 357 else if( n.equals( "a" ) ) 358 { 359 if( !isIgnorableWikiMarkupLink( e ) ) 360 { 361 if( e.getChild( "IMG" ) != null ) 362 { 363 printImage( e ); 364 } 365 else 366 { 367 String ref = e.getAttributeValue( "href" ); 368 if( ref == null ) 369 { 370 if( isUndefinedPageLink( e ) ) 371 { 372 m_out.print( "[" ); 373 print( e ); 374 m_out.print( "]" ); 375 } 376 else 377 { 378 print( e ); 379 } 380 } 381 else 382 { 383 ref = trimLink( ref ); 384 if( ref != null ) 385 { 386 if( ref.startsWith( "#" ) ) // This is a link to a footnote. 387 { 388 // convert "#ref-PageName-1" to just "1" 389 String href = ref.replaceFirst( "#ref-.+-(\\d+)", "$1" ); 390 391 // remove the brackets around "[1]" 392 String textValue = e.getValue().substring( 1, (e.getValue().length() - 1) ); 393 394 if( href.equals( textValue ) ){ // handles the simplest case. Example: [1] 395 print( e ); 396 } 397 else{ // handles the case where the link text is different from the href. Example: [something|1] 398 m_out.print( "[" + textValue + "|" + href + "]" ); 399 } 400 } 401 else 402 { 403 Map<String, String> augmentedWikiLinkAttributes = getAugmentedWikiLinkAttributes( e ); 404 405 m_out.print( "[" ); 406 print( e ); 407 if( !e.getTextTrim().equalsIgnoreCase( ref ) ) 408 { 409 m_out.print( "|" ); 410 print( ref ); 411 412 if( !augmentedWikiLinkAttributes.isEmpty() ) 413 { 414 m_out.print( "|" ); 415 416 String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); 417 m_out.print( augmentedWikiLink ); 418 } 419 } 420 else if( !augmentedWikiLinkAttributes.isEmpty() ) 421 { 422 // If the ref has the same value as the text and also if there 423 // are attributes, then just print: [ref|ref|attributes] . 424 m_out.print( "|" + ref + "|" ); 425 String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); 426 m_out.print( augmentedWikiLink ); 427 } 428 429 m_out.print( "]" ); 430 } 431 } 432 } 433 } 434 } 435 } 436 else if( n.equals( "b" ) || n.equals("strong") ) 437 { 438 m_out.print( "__" ); 439 print( e ); 440 m_out.print( "__" ); 441 } 442 else if( n.equals( "i" ) || n.equals("em") || n.equals( "address" ) ) 443 { 444 m_out.print( "''" ); 445 print( e ); 446 m_out.print( "''" ); 447 } 448 else if( n.equals( "u" ) ) 449 { 450 m_out.print( "%%( text-decoration:underline; )" ); 451 print( e ); 452 m_out.print( "/%" ); 453 } 454 else if( n.equals( "strike" ) ) 455 { 456 m_out.print( "%%strike " ); 457 print( e ); 458 m_out.print( "/%" ); 459 // NOTE: don't print a space before or after the double percents because that can break words into two. 460 // For example: %%(color:red)ABC%%%%(color:green)DEF%% is different from %%(color:red)ABC%% %%(color:green)DEF%% 461 } 462 else if( n.equals( "sup" ) ) 463 { 464 m_out.print( "%%sup " ); 465 print( e ); 466 m_out.print( "/%" ); 467 } 468 else if( n.equals( "sub" ) ) 469 { 470 m_out.print( "%%sub " ); 471 print( e ); 472 m_out.print( "/%" ); 473 } 474 else if( n.equals("dl") ) 475 { 476 m_out.print( "\n" ); 477 print( e ); 478 479 // print a newline after the definition list. If we don't, 480 // it may cause problems for the subsequent element. 481 m_out.print( "\n" ); 482 } 483 else if( n.equals("dt") ) 484 { 485 m_out.print( ";" ); 486 print( e ); 487 } 488 else if( n.equals("dd") ) 489 { 490 m_out.print( ":" ); 491 print( e ); 492 } 493 else if( n.equals( "ul" ) ) 494 { 495 m_out.println(); 496 m_liStack.push( "*" ); 497 print( e ); 498 m_liStack.pop(); 499 } 500 else if( n.equals( "ol" ) ) 501 { 502 m_out.println(); 503 m_liStack.push( "#" ); 504 print( e ); 505 m_liStack.pop(); 506 } 507 else if( n.equals( "li" ) ) 508 { 509 m_out.print( m_liStack + " " ); 510 print( e ); 511 512 // The following line assumes that the XHTML has been "pretty-printed" 513 // (newlines separate child elements from their parents). 514 boolean lastListItem = base.indexOf( e ) == ( base.getContentSize() - 2 ); 515 boolean sublistItem = m_liStack.toString().length() > 1; 516 517 // only print a newline if this <li> element is not the last item within a sublist. 518 if( !sublistItem || !lastListItem ) 519 { 520 m_out.println(); 521 } 522 } 523 else if( n.equals( "pre" ) ) 524 { 525 m_out.print( "\n{{{" ); // start JSPWiki "code blocks" on its own line 526 m_preStack.push(); 527 print( e ); 528 m_preStack.pop(); 529 530 // print a newline after the closing braces 531 // to avoid breaking any subsequent wiki markup that follows. 532 m_out.print( "}}}\n" ); 533 } 534 else if( n.equals( "code" ) || n.equals( "tt" ) ) 535 { 536 m_out.print( "{{" ); 537 m_preStack.push(); 538 print( e ); 539 m_preStack.pop(); 540 m_out.print( "}}" ); 541 // NOTE: don't print a newline after the closing brackets because if the Text is inside 542 // a table or list, it would break it if there was a subsequent row or list item. 543 } 544 else if( n.equals( "img" ) ) 545 { 546 if( !isIgnorableWikiMarkupLink( e ) ) 547 { 548 m_out.print( "[" ); 549 print( trimLink( e.getAttributeValue( "src" ) ) ); 550 m_out.print( "]" ); 551 } 552 } 553 else if( n.equals( "form" ) ) 554 { 555 // remove the hidden input where name="formname" since a new one will be generated again when the xhtml is rendered. 556 Element formName = (Element)XPath.selectSingleNode( e, "INPUT[@name='formname']" ); 557 if( formName != null ) 558 { 559 formName.detach(); 560 } 561 562 String name = e.getAttributeValue( "name" ); 563 564 m_out.print( "\n[{FormOpen" ); 565 566 if( name != null ) 567 { 568 m_out.print( " form='" + name + "'" ); 569 } 570 571 m_out.print( "}]\n" ); 572 573 print( e ); 574 m_out.print( "\n[{FormClose}]\n" ); 575 } 576 else if( n.equals( "input" ) ) 577 { 578 String type = e.getAttributeValue( "type" ); 579 String name = e.getAttributeValue( "name" ); 580 String value = e.getAttributeValue( "value" ); 581 String checked = e.getAttributeValue( "checked" ); 582 583 m_out.print( "[{FormInput" ); 584 585 if( type != null ) 586 { 587 m_out.print( " type='" + type + "'" ); 588 } 589 if( name != null ) 590 { 591 // remove the "nbf_" that was prepended since new one will be generated again when the xhtml is rendered. 592 if( name.startsWith( "nbf_" ) ) 593 { 594 name = name.substring( 4, name.length( )); 595 } 596 m_out.print( " name='" + name + "'" ); 597 } 598 if( value != null && !value.equals( "" ) ) 599 { 600 m_out.print( " value='" + value + "'" ); 601 } 602 if( checked != null ) 603 { 604 m_out.print( " checked='" + checked + "'" ); 605 } 606 607 m_out.print( "}]" ); 608 609 print( e ); 610 } 611 else if( n.equals( "textarea" ) ) 612 { 613 String name = e.getAttributeValue( "name" ); 614 String rows = e.getAttributeValue( "rows" ); 615 String cols = e.getAttributeValue( "cols" ); 616 617 m_out.print( "[{FormTextarea" ); 618 619 if( name != null ) 620 { 621 if( name.startsWith( "nbf_" ) ) 622 { 623 name = name.substring( 4, name.length( )); 624 } 625 m_out.print( " name='" + name + "'" ); 626 } 627 if( rows != null ) 628 { 629 m_out.print( " rows='" + rows + "'" ); 630 } 631 if( cols != null ) 632 { 633 m_out.print( " cols='" + cols + "'" ); 634 } 635 636 m_out.print( "}]" ); 637 print( e ); 638 } 639 else if( n.equals( "select" ) ) 640 { 641 String name = e.getAttributeValue( "name" ); 642 643 m_out.print( "[{FormSelect" ); 644 645 if( name != null ) 646 { 647 if( name.startsWith( "nbf_" ) ) 648 { 649 name = name.substring( 4, name.length( )); 650 } 651 m_out.print( " name='" + name + "'" ); 652 } 653 654 m_out.print( " value='" ); 655 print( e ); 656 m_out.print( "'}]" ); 657 } 658 else if( n.equals( "option" ) ) 659 { 660 // If this <option> element isn't the second child element within the parent <select> 661 // element, then we need to print a semicolon as a separator. (The first child element 662 // is expected to be a newline character which is at index of 0). 663 if( base.indexOf( e ) != 1 ) 664 { 665 m_out.print( ";" ); 666 } 667 668 Attribute selected = e.getAttribute( "selected" ); 669 if( selected != null ) 670 { 671 m_out.print( "*" ); 672 } 673 674 String value = e.getAttributeValue( "value" ); 675 if( value != null ) 676 { 677 m_out.print( value ); 678 } 679 else 680 { 681 print( e ); 682 } 683 } 684 else 685 { 686 print( e ); 687 } 688 } 689 else 690 { 691 print( c ); 692 } 693 } 694 } 695 696 private void printImage( Element base ) throws JDOMException 697 { 698 Element child = (Element)XPath.selectSingleNode( base, "TBODY/TR/TD/*" ); 699 if( child == null ) 700 { 701 child = base; 702 } 703 Element img; 704 String href; 705 Map<Object,Object> map = new ForgetNullValuesLinkedHashMap<>(); 706 if( child.getName().equals( "A" ) ) 707 { 708 img = child.getChild( "IMG" ); 709 href = child.getAttributeValue( "href" ); 710 } 711 else 712 { 713 img = child; 714 href = null; 715 } 716 if( img == null ) 717 { 718 return; 719 } 720 String src = trimLink( img.getAttributeValue( "src" ) ); 721 if( src == null ) 722 { 723 return; 724 } 725 map.put( "align", base.getAttributeValue( "align" ) ); 726 map.put( "height", img.getAttributeValue( "height" ) ); 727 map.put( "width", img.getAttributeValue( "width" ) ); 728 map.put( "alt", img.getAttributeValue( "alt" ) ); 729 map.put( "caption", emptyToNull( XPath.newInstance( "CAPTION" ).valueOf( base ) ) ); 730 map.put( "link", href ); 731 map.put( "border", img.getAttributeValue( "border" ) ); 732 map.put( "style", base.getAttributeValue( "style" ) ); 733 if( map.size() > 0 ) 734 { 735 m_out.print( "[{Image src='" + src + "'" ); 736 for( Iterator i = map.entrySet().iterator(); i.hasNext(); ) 737 { 738 Map.Entry entry = (Map.Entry)i.next(); 739 if( !entry.getValue().equals( "" ) ) 740 { 741 m_out.print( " " + entry.getKey() + "='" + entry.getValue() + "'" ); 742 } 743 } 744 m_out.print( "}]" ); 745 } 746 else 747 { 748 m_out.print( "[" + src + "]" ); 749 } 750 } 751 752 private String emptyToNull( String s ) 753 { 754 return s == null ? null : (s.replaceAll( "\\s", "" ).length() == 0 ? null : s); 755 } 756 757 private String propsToStyleString( Map styleProps ) 758 { 759 StringBuilder style = new StringBuilder(); 760 for( Iterator i = styleProps.entrySet().iterator(); i.hasNext(); ) 761 { 762 Map.Entry entry = (Map.Entry)i.next(); 763 style.append( " " ).append( entry.getKey() ).append( ": " ).append( entry.getValue() ).append( ";" ); 764 } 765 return style.toString(); 766 } 767 768 private boolean isIgnorableWikiMarkupLink( Element a ) 769 { 770 String ref = a.getAttributeValue( "href" ); 771 String clazz = a.getAttributeValue( "class" ); 772 return (ref != null && ref.startsWith( m_config.getPageInfoJsp() )) 773 || (clazz != null && clazz.trim().equalsIgnoreCase( m_config.getOutlink() )); 774 } 775 776 /** 777 * Checks if the link points to an undefined page. 778 */ 779 private boolean isUndefinedPageLink( Element a) 780 { 781 String classVal = a.getAttributeValue( "class" ); 782 783 return "createpage".equals( classVal ); 784 } 785 786 /** 787 * Returns a Map containing the valid augmented wiki link attributes. 788 */ 789 private Map<String,String> getAugmentedWikiLinkAttributes( Element a ) 790 { 791 Map<String,String> attributesMap = new HashMap<>(); 792 793 String id = a.getAttributeValue( "id" ); 794 if( id != null && !id.equals( "" ) ) 795 { 796 attributesMap.put( "id", id.replaceAll( "'", "\"" ) ); 797 } 798 799 String cssClass = a.getAttributeValue( "class" ); 800 if( cssClass != null && !cssClass.equals( "" ) 801 && !cssClass.matches( "wikipage|createpage|external|interwiki|attachment" ) ) 802 { 803 attributesMap.put( "class", cssClass.replaceAll( "'", "\"" ) ); 804 } 805 806 String style = a.getAttributeValue( "style" ); 807 if( style != null && !style.equals( "" ) ) 808 { 809 attributesMap.put( "style", style.replaceAll( "'", "\"" ) ); 810 } 811 812 String title = a.getAttributeValue( "title" ); 813 if( title != null && !title.equals( "" ) ) 814 { 815 attributesMap.put( "title", title.replaceAll( "'", "\"" ) ); 816 } 817 818 String lang = a.getAttributeValue( "lang" ); 819 if( lang != null && !lang.equals( "" ) ) 820 { 821 attributesMap.put( "lang", lang.replaceAll( "'", "\"" ) ); 822 } 823 824 String dir = a.getAttributeValue( "dir" ); 825 if( dir != null && !dir.equals( "" ) ) 826 { 827 attributesMap.put( "dir", dir.replaceAll( "'", "\"" ) ); 828 } 829 830 String charset = a.getAttributeValue( "charset" ); 831 if( charset != null && !charset.equals("") ) 832 { 833 attributesMap.put( "charset", charset.replaceAll( "'", "\"" ) ); 834 } 835 836 String type = a.getAttributeValue( "type" ); 837 if( type != null && !type.equals( "" ) ) 838 { 839 attributesMap.put( "type", type.replaceAll( "'", "\"" ) ); 840 } 841 842 String hreflang = a.getAttributeValue( "hreflang" ); 843 if( hreflang != null && !hreflang.equals( "" ) ) 844 { 845 attributesMap.put( "hreflang", hreflang.replaceAll( "'", "\"" ) ); 846 } 847 848 String rel = a.getAttributeValue( "rel" ); 849 if( rel != null && !rel.equals( "" ) ) 850 { 851 attributesMap.put( "rel", rel.replaceAll( "'", "\"" ) ); 852 } 853 854 String rev = a.getAttributeValue( "rev" ); 855 if( rev != null && !rev.equals( "" ) ) 856 { 857 attributesMap.put( "rev", rev.replaceAll( "'", "\"" ) ); 858 } 859 860 String accesskey = a.getAttributeValue( "accesskey" ); 861 if( accesskey != null && !accesskey.equals( "" ) ) 862 { 863 attributesMap.put( "accesskey", accesskey.replaceAll( "'", "\"" ) ); 864 } 865 866 String tabindex = a.getAttributeValue( "tabindex" ); 867 if( tabindex != null && !tabindex.equals( "" ) ) 868 { 869 attributesMap.put( "tabindex", tabindex.replaceAll( "'", "\"" ) ); 870 } 871 872 String target = a.getAttributeValue( "target" ); 873 if( target != null && !target.equals( "" ) ) 874 { 875 attributesMap.put( "target", target.replaceAll( "'", "\"" ) ); 876 } 877 878 return attributesMap; 879 } 880 881 /** 882 * Converts the entries in the map to a string for use in a wiki link. 883 */ 884 private String augmentedWikiLinkMapToString( Map attributesMap ) 885 { 886 StringBuilder sb = new StringBuilder(); 887 888 for ( Iterator itr = attributesMap.entrySet().iterator(); itr.hasNext(); ) 889 { 890 Map.Entry entry = (Map.Entry)itr.next(); 891 String attributeName = (String)entry.getKey(); 892 String attributeValue = (String)entry.getValue(); 893 894 sb.append( " " + attributeName + "='" + attributeValue + "'" ); 895 } 896 897 return sb.toString().trim(); 898 } 899 900 private Map getStylePropertiesLowerCase( Element base ) throws IOException 901 { 902 String n = base.getName().toLowerCase(); 903 904 //"font-weight: bold; font-style: italic;" 905 String style = base.getAttributeValue( "style" ); 906 if( style == null ) 907 { 908 style = ""; 909 } 910 911 if( n.equals( "p" ) || n.equals( "div" ) ) 912 { 913 String align = base.getAttributeValue( "align" ); 914 if( align != null ) 915 { 916 // only add the value of the align attribute if the text-align style didn't already exist. 917 if( style.indexOf( "text-align" ) == -1 ) 918 { 919 style = style + ";text-align:" + align + ";"; 920 } 921 } 922 } 923 924 925 926 if( n.equals( "font" ) ) 927 { 928 String color = base.getAttributeValue( "color" ); 929 String face = base.getAttributeValue( "face" ); 930 String size = base.getAttributeValue( "size" ); 931 if( color != null ) 932 { 933 style = style + "color:" + color + ";"; 934 } 935 if( face != null ) 936 { 937 style = style + "font-family:" + face + ";"; 938 } 939 if( size != null ) 940 { 941 if( size.equals( "1" ) ) 942 { 943 style = style + "font-size:xx-small;"; 944 } 945 else if( size.equals( "2" ) ) 946 { 947 style = style + "font-size:x-small;"; 948 } 949 else if( size.equals( "3" ) ) 950 { 951 style = style + "font-size:small;"; 952 } 953 else if( size.equals( "4" ) ) 954 { 955 style = style + "font-size:medium;"; 956 } 957 else if( size.equals( "5" ) ) 958 { 959 style = style + "font-size:large;"; 960 } 961 else if( size.equals( "6" ) ) 962 { 963 style = style + "font-size:x-large;"; 964 } 965 else if( size.equals( "7" ) ) 966 { 967 style = style + "font-size:xx-large;"; 968 } 969 } 970 } 971 972 if( style.equals( "" ) ) 973 { 974 return null; 975 } 976 977 style = style.replace( ';', '\n' ).toLowerCase(); 978 LinkedHashMap m = new LinkedHashMap(); 979 new PersistentMapDecorator( m ).load( new ByteArrayInputStream( style.getBytes() ) ); 980 return m; 981 } 982 983 private String trimLink( String ref ) 984 { 985 if( ref == null ) 986 { 987 return null; 988 } 989 try 990 { 991 ref = URLDecoder.decode( ref, UTF8 ); 992 ref = ref.trim(); 993 if( ref.startsWith( m_config.getAttachPage() ) ) 994 { 995 ref = ref.substring( m_config.getAttachPage().length() ); 996 } 997 if( ref.startsWith( m_config.getWikiJspPage() ) ) 998 { 999 ref = ref.substring( m_config.getWikiJspPage().length() ); 1000 1001 // Handle links with section anchors. 1002 // For example, we need to translate the html string "TargetPage#section-TargetPage-Heading2" 1003 // to this wiki string "TargetPage#Heading2". 1004 ref = ref.replaceFirst( ".+#section-(.+)-(.+)", "$1#$2" ); 1005 } 1006 if( ref.startsWith( m_config.getEditJspPage() ) ) 1007 { 1008 ref = ref.substring( m_config.getEditJspPage().length() ); 1009 } 1010 if( m_config.getPageName() != null ) 1011 { 1012 if( ref.startsWith( m_config.getPageName() ) ) 1013 { 1014 ref = ref.substring( m_config.getPageName().length() ); 1015 } 1016 } 1017 } 1018 catch ( UnsupportedEncodingException e ) 1019 { 1020 // Shouldn't happen... 1021 } 1022 return ref; 1023 } 1024 1025 // FIXME: These should probably be better used with java.util.Stack 1026 1027 private static class LiStack 1028 { 1029 1030 private StringBuffer m_li = new StringBuffer(); 1031 1032 public void push( String c ) 1033 { 1034 m_li.append( c ); 1035 } 1036 1037 public void pop() 1038 { 1039 m_li = m_li.deleteCharAt( m_li.length()-1 ); 1040 // m_li = m_li.substring( 0, m_li.length() - 1 ); 1041 } 1042 1043 @Override 1044 public String toString() 1045 { 1046 return m_li.toString(); 1047 } 1048 1049 } 1050 1051 private class PreStack 1052 { 1053 1054 private int m_pre = 0; 1055 1056 public boolean isPreMode() 1057 { 1058 return m_pre > 0; 1059 } 1060 1061 public void push() 1062 { 1063 m_pre++; 1064 m_outTimmer.setWhitespaceTrimMode( !isPreMode() ); 1065 } 1066 1067 public void pop() 1068 { 1069 m_pre--; 1070 m_outTimmer.setWhitespaceTrimMode( !isPreMode() ); 1071 } 1072 1073 } 1074 1075}