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 */
019 package org.apache.wiki.htmltowiki;
020
021 import java.io.ByteArrayInputStream;
022 import java.io.IOException;
023 import java.io.PrintWriter;
024 import java.io.UnsupportedEncodingException;
025 import java.net.URLDecoder;
026 import java.util.HashMap;
027 import java.util.Iterator;
028 import java.util.LinkedHashMap;
029 import java.util.Map;
030
031 import org.apache.commons.lang.StringEscapeUtils;
032 import org.jdom2.Element;
033 import org.jdom2.Attribute;
034 import org.jdom2.JDOMException;
035 import org.jdom2.Text;
036 import 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 */
042 public 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 StringBuffer style = new StringBuffer();
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 StringBuffer sb = new StringBuffer();
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 }