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" ); 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( styleProps != null ) 152 { 153 String fontFamily = (String)styleProps.get( "font-family" ); 154 String whiteSpace = (String)styleProps.get( "white-space" ); 155 if( fontFamily != null && ( fontFamily.indexOf( "monospace" ) >= 0 156 && whiteSpace != null && whiteSpace.indexOf( "pre" ) >= 0 ) ) 157 { 158 styleProps.remove( "font-family" ); 159 styleProps.remove( "white-space" ); 160 monospace = true; 161 } 162 163 String weight = (String)styleProps.remove( "font-weight" ); 164 String style = (String)styleProps.remove( "font-style" ); 165 166 if( n.equals( "p" ) ) 167 { 168 // change it so we can print out the css styles for <p> 169 n = "div"; 170 } 171 172 italic = "oblique".equals( style ) || "italic".equals( style ); 173 bold = "bold".equals( weight ) || "bolder".equals( weight ); 174 if( !styleProps.isEmpty() ) 175 { 176 cssSpecial = propsToStyleString( styleProps ); 177 } 178 } 179 if( cssClass != null && !ignoredCssClass ) 180 { 181 if( n.equals( "div" ) ) 182 { 183 m_out.print( "\n%%" + cssClass + " \n" ); 184 } 185 else if( n.equals( "span" ) ) 186 { 187 m_out.print( "%%" + cssClass + " " ); 188 } 189 } 190 if( bold ) 191 { 192 m_out.print( "__" ); 193 } 194 if( italic ) 195 { 196 m_out.print( "''" ); 197 } 198 if( monospace ) 199 { 200 m_out.print( "{{{" ); 201 m_preStack.push(); 202 } 203 if( cssSpecial != null ) 204 { 205 if( n.equals( "div" ) ) 206 { 207 m_out.print( "\n%%(" + cssSpecial + " )\n" ); 208 } 209 else 210 { 211 m_out.print( "%%(" + cssSpecial + " )" ); 212 } 213 } 214 printChildren( base ); 215 if( cssSpecial != null ) 216 { 217 if( n.equals( "div" ) ) 218 { 219 m_out.print( "\n/%\n" ); 220 } 221 else 222 { 223 m_out.print( "/%" ); 224 } 225 } 226 if( monospace ) 227 { 228 m_preStack.pop(); 229 m_out.print( "}}}" ); 230 } 231 if( italic ) 232 { 233 m_out.print( "''" ); 234 } 235 if( bold ) 236 { 237 m_out.print( "__" ); 238 } 239 if( cssClass != null && !ignoredCssClass ) 240 { 241 if( n.equals( "div" ) ) 242 { 243 m_out.print( "\n/%\n" ); 244 } 245 else if( n.equals( "span" ) ) 246 { 247 m_out.print( "/%" ); 248 } 249 } 250 } 251 } 252 } 253 254 private void printChildren( Element base ) throws IOException, JDOMException 255 { 256 for( Iterator< Content > i = base.getContent().iterator(); i.hasNext(); ) 257 { 258 Content c = i.next(); 259 if( c instanceof Element ) 260 { 261 Element e = (Element)c; 262 String n = e.getName().toLowerCase(); 263 if( n.equals( "h1" ) ) 264 { 265 m_out.print( "\n!!! " ); 266 print( e ); 267 m_out.println(); 268 } 269 else if( n.equals( "h2" ) ) 270 { 271 m_out.print( "\n!!! " ); 272 print( e ); 273 m_out.println(); 274 } 275 else if( n.equals( "h3" ) ) 276 { 277 m_out.print( "\n!! " ); 278 print( e ); 279 m_out.println(); 280 } 281 else if( n.equals( "h4" ) ) 282 { 283 m_out.print( "\n! " ); 284 print( e ); 285 m_out.println(); 286 } 287 else if( n.equals( "p" ) ) 288 { 289 if( e.getContentSize() != 0 ) // we don't want to print empty elements: <p></p> 290 { 291 m_out.println(); 292 print( e ); 293 m_out.println(); 294 } 295 } 296 else if( n.equals( "br" ) ) 297 { 298 if( m_preStack.isPreMode() ) 299 { 300 m_out.println(); 301 } 302 else 303 { 304 String parentElementName = base.getName().toLowerCase(); 305 306 // 307 // To beautify the generated wiki markup, we print a newline character after a linebreak. 308 // It's only safe to do this when the parent element is a <p> or <div>; when the parent 309 // element is a table cell or list item, a newline character would break the markup. 310 // We also check that this isn't being done inside a plugin body. 311 // 312 if( parentElementName.matches( "p|div" ) 313 && !base.getText().matches( "(?s).*\\[\\{.*\\}\\].*" ) ) 314 { 315 m_out.print( " \\\\\n" ); 316 } 317 else 318 { 319 m_out.print( " \\\\" ); 320 } 321 } 322 print( e ); 323 } 324 else if( n.equals( "hr" ) ) 325 { 326 m_out.println(); 327 print( "----" ); 328 print( e ); 329 m_out.println(); 330 } 331 else if( n.equals( "table" ) ) 332 { 333 if( !m_outTimmer.isCurrentlyOnLineBegin() ) 334 { 335 m_out.println(); 336 } 337 print( e ); 338 } 339 else if( n.equals( "tr" ) ) 340 { 341 print( e ); 342 m_out.println(); 343 } 344 else if( n.equals( "td" ) ) 345 { 346 m_out.print( "| " ); 347 print( e ); 348 if( !m_preStack.isPreMode() ) 349 { 350 print( " " ); 351 } 352 } 353 else if( n.equals( "th" ) ) 354 { 355 m_out.print( "|| " ); 356 print( e ); 357 if( !m_preStack.isPreMode() ) 358 { 359 print( " " ); 360 } 361 } 362 else if( n.equals( "a" ) ) 363 { 364 if( !isIgnorableWikiMarkupLink( e ) ) 365 { 366 if( e.getChild( "IMG" ) != null ) 367 { 368 printImage( e ); 369 } 370 else 371 { 372 String ref = e.getAttributeValue( "href" ); 373 if( ref == null ) 374 { 375 if( isUndefinedPageLink( e ) ) 376 { 377 m_out.print( "[" ); 378 print( e ); 379 m_out.print( "]" ); 380 } 381 else 382 { 383 print( e ); 384 } 385 } 386 else 387 { 388 ref = trimLink( ref ); 389 if( ref != null ) 390 { 391 if( ref.startsWith( "#" ) ) // This is a link to a footnote. 392 { 393 // convert "#ref-PageName-1" to just "1" 394 String href = ref.replaceFirst( "#ref-.+-(\\d+)", "$1" ); 395 396 // remove the brackets around "[1]" 397 String textValue = e.getValue().substring( 1, (e.getValue().length() - 1) ); 398 399 if( href.equals( textValue ) ){ // handles the simplest case. Example: [1] 400 print( e ); 401 } 402 else{ // handles the case where the link text is different from the href. Example: [something|1] 403 m_out.print( "[" + textValue + "|" + href + "]" ); 404 } 405 } 406 else 407 { 408 Map<String, String> augmentedWikiLinkAttributes = getAugmentedWikiLinkAttributes( e ); 409 410 m_out.print( "[" ); 411 print( e ); 412 if( !e.getTextTrim().equalsIgnoreCase( ref ) ) 413 { 414 m_out.print( "|" ); 415 print( ref ); 416 417 if( !augmentedWikiLinkAttributes.isEmpty() ) 418 { 419 m_out.print( "|" ); 420 421 String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); 422 m_out.print( augmentedWikiLink ); 423 } 424 } 425 else if( !augmentedWikiLinkAttributes.isEmpty() ) 426 { 427 // If the ref has the same value as the text and also if there 428 // are attributes, then just print: [ref|ref|attributes] . 429 m_out.print( "|" + ref + "|" ); 430 String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); 431 m_out.print( augmentedWikiLink ); 432 } 433 434 m_out.print( "]" ); 435 } 436 } 437 } 438 } 439 } 440 } 441 else if( n.equals( "b" ) || n.equals("strong") ) 442 { 443 m_out.print( "__" ); 444 print( e ); 445 m_out.print( "__" ); 446 } 447 else if( n.equals( "i" ) || n.equals("em") || n.equals( "address" ) ) 448 { 449 m_out.print( "''" ); 450 print( e ); 451 m_out.print( "''" ); 452 } 453 else if( n.equals( "u" ) ) 454 { 455 m_out.print( "%%( text-decoration:underline; )" ); 456 print( e ); 457 m_out.print( "/%" ); 458 } 459 else if( n.equals( "strike" ) ) 460 { 461 m_out.print( "%%strike " ); 462 print( e ); 463 m_out.print( "/%" ); 464 // NOTE: don't print a space before or after the double percents because that can break words into two. 465 // For example: %%(color:red)ABC%%%%(color:green)DEF%% is different from %%(color:red)ABC%% %%(color:green)DEF%% 466 } 467 else if( n.equals( "sup" ) ) 468 { 469 m_out.print( "%%sup " ); 470 print( e ); 471 m_out.print( "/%" ); 472 } 473 else if( n.equals( "sub" ) ) 474 { 475 m_out.print( "%%sub " ); 476 print( e ); 477 m_out.print( "/%" ); 478 } 479 else if( n.equals("dl") ) 480 { 481 m_out.print( "\n" ); 482 print( e ); 483 484 // print a newline after the definition list. If we don't, 485 // it may cause problems for the subsequent element. 486 m_out.print( "\n" ); 487 } 488 else if( n.equals("dt") ) 489 { 490 m_out.print( ";" ); 491 print( e ); 492 } 493 else if( n.equals("dd") ) 494 { 495 m_out.print( ":" ); 496 print( e ); 497 } 498 else if( n.equals( "ul" ) ) 499 { 500 m_out.println(); 501 m_liStack.push( "*" ); 502 print( e ); 503 m_liStack.pop(); 504 } 505 else if( n.equals( "ol" ) ) 506 { 507 m_out.println(); 508 m_liStack.push( "#" ); 509 print( e ); 510 m_liStack.pop(); 511 } 512 else if( n.equals( "li" ) ) 513 { 514 m_out.print( m_liStack + " " ); 515 print( e ); 516 517 // The following line assumes that the XHTML has been "pretty-printed" 518 // (newlines separate child elements from their parents). 519 boolean lastListItem = base.indexOf( e ) == ( base.getContentSize() - 2 ); 520 boolean sublistItem = m_liStack.toString().length() > 1; 521 522 // only print a newline if this <li> element is not the last item within a sublist. 523 if( !sublistItem || !lastListItem ) 524 { 525 m_out.println(); 526 } 527 } 528 else if( n.equals( "pre" ) ) 529 { 530 m_out.print( "\n{{{" ); // start JSPWiki "code blocks" on its own line 531 m_preStack.push(); 532 print( e ); 533 m_preStack.pop(); 534 535 // print a newline after the closing braces 536 // to avoid breaking any subsequent wiki markup that follows. 537 m_out.print( "}}}\n" ); 538 } 539 else if( n.equals( "code" ) || n.equals( "tt" ) ) 540 { 541 m_out.print( "{{" ); 542 m_preStack.push(); 543 print( e ); 544 m_preStack.pop(); 545 m_out.print( "}}" ); 546 // NOTE: don't print a newline after the closing brackets because if the Text is inside 547 // a table or list, it would break it if there was a subsequent row or list item. 548 } 549 else if( n.equals( "img" ) ) 550 { 551 if( !isIgnorableWikiMarkupLink( e ) ) 552 { 553 m_out.print( "[" ); 554 print( trimLink( e.getAttributeValue( "src" ) ) ); 555 m_out.print( "]" ); 556 } 557 } 558 else if( n.equals( "form" ) ) 559 { 560 // remove the hidden input where name="formname" since a new one will be generated again when the xhtml is rendered. 561 Element formName = (Element)XPath.selectSingleNode( e, "INPUT[@name='formname']" ); 562 if( formName != null ) 563 { 564 formName.detach(); 565 } 566 567 String name = e.getAttributeValue( "name" ); 568 569 m_out.print( "\n[{FormOpen" ); 570 571 if( name != null ) 572 { 573 m_out.print( " form='" + name + "'" ); 574 } 575 576 m_out.print( "}]\n" ); 577 578 print( e ); 579 m_out.print( "\n[{FormClose}]\n" ); 580 } 581 else if( n.equals( "input" ) ) 582 { 583 String type = e.getAttributeValue( "type" ); 584 String name = e.getAttributeValue( "name" ); 585 String value = e.getAttributeValue( "value" ); 586 String checked = e.getAttributeValue( "checked" ); 587 588 m_out.print( "[{FormInput" ); 589 590 if( type != null ) 591 { 592 m_out.print( " type='" + type + "'" ); 593 } 594 if( name != null ) 595 { 596 // remove the "nbf_" that was prepended since new one will be generated again when the xhtml is rendered. 597 if( name.startsWith( "nbf_" ) ) 598 { 599 name = name.substring( 4, name.length( )); 600 } 601 m_out.print( " name='" + name + "'" ); 602 } 603 if( value != null && !value.equals( "" ) ) 604 { 605 m_out.print( " value='" + value + "'" ); 606 } 607 if( checked != null ) 608 { 609 m_out.print( " checked='" + checked + "'" ); 610 } 611 612 m_out.print( "}]" ); 613 614 print( e ); 615 } 616 else if( n.equals( "textarea" ) ) 617 { 618 String name = e.getAttributeValue( "name" ); 619 String rows = e.getAttributeValue( "rows" ); 620 String cols = e.getAttributeValue( "cols" ); 621 622 m_out.print( "[{FormTextarea" ); 623 624 if( name != null ) 625 { 626 if( name.startsWith( "nbf_" ) ) 627 { 628 name = name.substring( 4, name.length( )); 629 } 630 m_out.print( " name='" + name + "'" ); 631 } 632 if( rows != null ) 633 { 634 m_out.print( " rows='" + rows + "'" ); 635 } 636 if( cols != null ) 637 { 638 m_out.print( " cols='" + cols + "'" ); 639 } 640 641 m_out.print( "}]" ); 642 print( e ); 643 } 644 else if( n.equals( "select" ) ) 645 { 646 String name = e.getAttributeValue( "name" ); 647 648 m_out.print( "[{FormSelect" ); 649 650 if( name != null ) 651 { 652 if( name.startsWith( "nbf_" ) ) 653 { 654 name = name.substring( 4, name.length( )); 655 } 656 m_out.print( " name='" + name + "'" ); 657 } 658 659 m_out.print( " value='" ); 660 print( e ); 661 m_out.print( "'}]" ); 662 } 663 else if( n.equals( "option" ) ) 664 { 665 // If this <option> element isn't the second child element within the parent <select> 666 // element, then we need to print a semicolon as a separator. (The first child element 667 // is expected to be a newline character which is at index of 0). 668 if( base.indexOf( e ) != 1 ) 669 { 670 m_out.print( ";" ); 671 } 672 673 Attribute selected = e.getAttribute( "selected" ); 674 if( selected != null ) 675 { 676 m_out.print( "*" ); 677 } 678 679 String value = e.getAttributeValue( "value" ); 680 if( value != null ) 681 { 682 m_out.print( value ); 683 } 684 else 685 { 686 print( e ); 687 } 688 } 689 else 690 { 691 print( e ); 692 } 693 } 694 else 695 { 696 print( c ); 697 } 698 } 699 } 700 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 "createpage".equals( classVal ); 789 } 790 791 /** 792 * Returns a Map containing the valid augmented wiki link attributes. 793 */ 794 private Map<String,String> getAugmentedWikiLinkAttributes( Element a ) 795 { 796 Map<String,String> attributesMap = new HashMap<>(); 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 @Override 1049 public String toString() 1050 { 1051 return m_li.toString(); 1052 } 1053 1054 } 1055 1056 private class PreStack 1057 { 1058 1059 private int m_pre = 0; 1060 1061 public boolean isPreMode() 1062 { 1063 return m_pre > 0; 1064 } 1065 1066 public void push() 1067 { 1068 m_pre++; 1069 m_outTimmer.setWhitespaceTrimMode( !isPreMode() ); 1070 } 1071 1072 public void pop() 1073 { 1074 m_pre--; 1075 m_outTimmer.setWhitespaceTrimMode( !isPreMode() ); 1076 } 1077 1078 } 1079 1080}