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