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