001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2007 JSPWiki Development Group
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.htmltowiki;
021:
022: import java.io.ByteArrayInputStream;
023: import java.io.IOException;
024: import java.io.PrintWriter;
025: import java.io.UnsupportedEncodingException;
026: import java.net.URLDecoder;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.LinkedHashMap;
030: import java.util.Map;
031:
032: import org.apache.commons.lang.StringEscapeUtils;
033: import org.jdom.Element;
034: import org.jdom.Attribute;
035: import org.jdom.JDOMException;
036: import org.jdom.Text;
037: import org.jdom.xpath.XPath;
038:
039: /**
040: * Converting XHtml to Wiki Markup
041: *
042: * @author Sebastian Baltes (sbaltes@gmx.com)
043: */
044: public class XHtmlElementToWikiTranslator {
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: public XHtmlElementToWikiTranslator(Element base)
058: throws IOException, JDOMException {
059: this (base, new XHtmlToWikiConfig());
060: }
061:
062: public XHtmlElementToWikiTranslator(Element base,
063: XHtmlToWikiConfig config) throws IOException, JDOMException {
064: this .m_config = config;
065: m_outTimmer = new WhitespaceTrimWriter();
066: m_out = new PrintWriter(m_outTimmer);
067: print(base);
068: }
069:
070: public String getWikiString() {
071: return m_outTimmer.toString();
072: }
073:
074: private void print(String s) {
075: s = StringEscapeUtils.unescapeHtml(s);
076: m_out.print(s);
077: }
078:
079: private void print(Object element) throws IOException,
080: JDOMException {
081: if (element instanceof Text) {
082: Text t = (Text) element;
083: String s = t.getText();
084: if (m_preStack.isPreMode()) {
085: m_out.print(s);
086: } else {
087: // remove all "line terminator" characters
088: s = s
089: .replaceAll("[\\r\\n\\f\\u0085\\u2028\\u2029]",
090: "");
091: m_out.print(s);
092: }
093: } else if (element instanceof Element) {
094: Element base = (Element) element;
095: String n = base.getName().toLowerCase();
096: if ("imageplugin".equals(base.getAttributeValue("class"))) {
097: printImage(base);
098: } else if ("wikiform".equals(base
099: .getAttributeValue("class"))) {
100: // only print the children if the div's class="wikiform", but not the div itself.
101: printChildren(base);
102: } else {
103: boolean bold = false;
104: boolean italic = false;
105: boolean monospace = false;
106: String cssSpecial = null;
107: String cssClass = base.getAttributeValue("class");
108:
109: // accomodate a FCKeditor bug with Firefox: when a link is removed, it becomes <span class="wikipage">text</span>.
110: boolean ignoredCssClass = cssClass != null
111: && cssClass
112: .matches("wikipage|createpage|external|interwiki|attachment");
113:
114: Map styleProps = null;
115:
116: // Only get the styles if it's not a link element. Styles for link elements are
117: // handled as an AugmentedWikiLink instead.
118: if (!n.equals("a")) {
119: styleProps = getStylePropertiesLowerCase(base);
120: }
121:
122: if (styleProps != null) {
123: String fontFamily = (String) styleProps
124: .get("font-family");
125: String whiteSpace = (String) styleProps
126: .get("white-space");
127: if (fontFamily != null
128: && (fontFamily.indexOf("monospace") >= 0
129: && whiteSpace != null && whiteSpace
130: .indexOf("pre") >= 0)) {
131: styleProps.remove("font-family");
132: styleProps.remove("white-space");
133: monospace = true;
134: }
135:
136: String weight = (String) styleProps
137: .remove("font-weight");
138: String style = (String) styleProps
139: .remove("font-style");
140:
141: if (n.equals("p")) {
142: // change it so we can print out the css styles for <p>
143: n = "div";
144: }
145:
146: italic = "oblique".equals(style)
147: || "italic".equals(style);
148: bold = "bold".equals(weight)
149: || "bolder".equals(weight);
150: if (!styleProps.isEmpty()) {
151: cssSpecial = propsToStyleString(styleProps);
152: }
153: }
154: if (cssClass != null && !ignoredCssClass) {
155: if (n.equals("div")) {
156: m_out.print("\n%%" + cssClass + " \n");
157: } else if (n.equals("span")) {
158: m_out.print("%%" + cssClass + " ");
159: }
160: }
161: if (bold) {
162: m_out.print("__");
163: }
164: if (italic) {
165: m_out.print("''");
166: }
167: if (monospace) {
168: m_out.print("{{{");
169: m_preStack.push();
170: }
171: if (cssSpecial != null) {
172: if (n.equals("div")) {
173: m_out.print("\n%%(" + cssSpecial + " )\n");
174: } else {
175: m_out.print("%%(" + cssSpecial + " )");
176: }
177: }
178: printChildren(base);
179: if (cssSpecial != null) {
180: if (n.equals("div")) {
181: m_out.print("\n%%\n");
182: } else {
183: m_out.print("%%");
184: }
185: }
186: if (monospace) {
187: m_preStack.pop();
188: m_out.print("}}}");
189: }
190: if (italic) {
191: m_out.print("''");
192: }
193: if (bold) {
194: m_out.print("__");
195: }
196: if (cssClass != null && !ignoredCssClass) {
197: if (n.equals("div")) {
198: m_out.print("\n%%\n");
199: } else if (n.equals("span")) {
200: m_out.print("%%");
201: }
202: }
203: }
204: }
205: }
206:
207: private void printChildren(Element base) throws IOException,
208: JDOMException {
209: for (Iterator i = base.getContent().iterator(); i.hasNext();) {
210: Object c = i.next();
211: if (c instanceof Element) {
212: Element e = (Element) c;
213: String n = e.getName().toLowerCase();
214: if (n.equals("h1")) {
215: m_out.print("\n!!! ");
216: print(e);
217: m_out.println();
218: } else if (n.equals("h2")) {
219: m_out.print("\n!!! ");
220: print(e);
221: m_out.println();
222: } else if (n.equals("h3")) {
223: m_out.print("\n!! ");
224: print(e);
225: m_out.println();
226: } else if (n.equals("h4")) {
227: m_out.print("\n! ");
228: print(e);
229: m_out.println();
230: } else if (n.equals("p")) {
231: if (e.getContentSize() != 0) // we don't want to print empty elements: <p></p>
232: {
233: m_out.println();
234: print(e);
235: m_out.println();
236: }
237: } else if (n.equals("br")) {
238: if (m_preStack.isPreMode()) {
239: m_out.println();
240: } else {
241: String parentElementName = base.getName()
242: .toLowerCase();
243:
244: //
245: // To beautify the generated wiki markup, we print a newline character after a linebreak.
246: // It's only safe to do this when the parent element is a <p> or <div>; when the parent
247: // element is a table cell or list item, a newline character would break the markup.
248: // We also check that this isn't being done inside a plugin body.
249: //
250: if (parentElementName.matches("p|div")
251: && !base.getText().matches(
252: "(?s).*\\[\\{.*\\}\\].*")) {
253: m_out.print(" \\\\\n");
254: } else {
255: m_out.print(" \\\\");
256: }
257: }
258: print(e);
259: } else if (n.equals("hr")) {
260: m_out.println();
261: print("----");
262: print(e);
263: m_out.println();
264: } else if (n.equals("table")) {
265: if (!m_outTimmer.isCurrentlyOnLineBegin()) {
266: m_out.println();
267: }
268: print(e);
269: } else if (n.equals("tr")) {
270: print(e);
271: m_out.println();
272: } else if (n.equals("td")) {
273: m_out.print("| ");
274: print(e);
275: if (!m_preStack.isPreMode()) {
276: print(" ");
277: }
278: } else if (n.equals("th")) {
279: m_out.print("|| ");
280: print(e);
281: if (!m_preStack.isPreMode()) {
282: print(" ");
283: }
284: } else if (n.equals("a")) {
285: if (!isIgnorableWikiMarkupLink(e)) {
286: if (e.getChild("IMG") != null) {
287: printImage(e);
288: } else {
289: String ref = e.getAttributeValue("href");
290: if (ref == null) {
291: if (isUndefinedPageLink(e)) {
292: m_out.print("[");
293: print(e);
294: m_out.print("]");
295: } else {
296: print(e);
297: }
298: } else {
299: ref = trimLink(ref);
300: if (ref != null) {
301: if (ref.startsWith("#")) {
302: print(e);
303: } else {
304: Map augmentedWikiLinkAttributes = getAugmentedWikiLinkAttributes(e);
305:
306: m_out.print("[");
307: print(e);
308: if (!e.getTextTrim()
309: .equalsIgnoreCase(ref)) {
310: m_out.print("|");
311: print(ref);
312:
313: if (!augmentedWikiLinkAttributes
314: .isEmpty()) {
315: m_out.print("|");
316:
317: String augmentedWikiLink = augmentedWikiLinkMapToString(augmentedWikiLinkAttributes);
318: m_out
319: .print(augmentedWikiLink);
320: }
321: } else if (!augmentedWikiLinkAttributes
322: .isEmpty()) {
323: // If the ref has the same value as the text and also if there
324: // are attributes, then just print: [ref|ref|attributes] .
325: m_out
326: .print("|" + ref
327: + "|");
328: String augmentedWikiLink = augmentedWikiLinkMapToString(augmentedWikiLinkAttributes);
329: m_out
330: .print(augmentedWikiLink);
331: }
332:
333: m_out.print("]");
334: }
335: }
336: }
337: }
338: }
339: } else if (n.equals("b") || n.equals("strong")) {
340: m_out.print("__");
341: print(e);
342: m_out.print("__");
343: } else if (n.equals("i") || n.equals("em")
344: || n.equals("address")) {
345: m_out.print("''");
346: print(e);
347: m_out.print("''");
348: } else if (n.equals("u")) {
349: m_out.print("%%( text-decoration:underline; )");
350: print(e);
351: m_out.print("%%");
352: } else if (n.equals("strike")) {
353: m_out.print("%%strike ");
354: print(e);
355: m_out.print("%%");
356: // NOTE: don't print a space before or after the double percents because that can break words into two.
357: // For example: %%(color:red)ABC%%%%(color:green)DEF%% is different from %%(color:red)ABC%% %%(color:green)DEF%%
358: } else if (n.equals("sup")) {
359: m_out.print("%%sup ");
360: print(e);
361: m_out.print("%%");
362: } else if (n.equals("sub")) {
363: m_out.print("%%sub ");
364: print(e);
365: m_out.print("%%");
366: } else if (n.equals("dl")) {
367: m_out.print("\n");
368: print(e);
369:
370: // print a newline after the definition list. If we don't,
371: // it may cause problems for the subsequent element.
372: m_out.print("\n");
373: } else if (n.equals("dt")) {
374: m_out.print(";");
375: print(e);
376: } else if (n.equals("dd")) {
377: m_out.print(":");
378: print(e);
379: } else if (n.equals("ul")) {
380: m_out.println();
381: m_liStack.push("*");
382: print(e);
383: m_liStack.pop();
384: } else if (n.equals("ol")) {
385: m_out.println();
386: m_liStack.push("#");
387: print(e);
388: m_liStack.pop();
389: } else if (n.equals("li")) {
390: m_out.print(m_liStack + " ");
391: print(e);
392:
393: // The following line assumes that the XHTML has been "pretty-printed"
394: // (newlines separate child elements from their parents).
395: boolean lastListItem = base.indexOf(e) == (base
396: .getContentSize() - 2);
397: boolean sublistItem = m_liStack.toString().length() > 1;
398:
399: // only print a newline if this <li> element is not the last item within a sublist.
400: if (!sublistItem || !lastListItem) {
401: m_out.println();
402: }
403: } else if (n.equals("pre")) {
404: m_out.print("\n{{{\n"); // start JSPWiki "code blocks" on its own line
405: m_preStack.push();
406: print(e);
407: m_preStack.pop();
408:
409: // print a newline before the closing braces for aesthetics and a newline after it
410: // to avoid breaking any subsequent wiki markup that follows.
411: m_out.print("\n}}}\n");
412: } else if (n.equals("code") || n.equals("tt")) {
413: m_out.print("{{");
414: m_preStack.push();
415: print(e);
416: m_preStack.pop();
417: m_out.print("}}");
418: // NOTE: don't print a newline after the closing brackets because if the Text is inside
419: // a table or list, it would break it if there was a subsequent row or list item.
420: } else if (n.equals("img")) {
421: if (!isIgnorableWikiMarkupLink(e)) {
422: m_out.print("[");
423: print(trimLink(e.getAttributeValue("src")));
424: m_out.print("]");
425: }
426: } else if (n.equals("form")) {
427: // remove the hidden input where name="formname" since a new one will be generated again when the xhtml is rendered.
428: Element formName = (Element) XPath
429: .selectSingleNode(e,
430: "INPUT[@name='formname']");
431: if (formName != null) {
432: formName.detach();
433: }
434:
435: String name = e.getAttributeValue("name");
436:
437: m_out.print("\n[{FormOpen");
438:
439: if (name != null) {
440: m_out.print(" form='" + name + "'");
441: }
442:
443: m_out.print("}]\n");
444:
445: print(e);
446: m_out.print("\n[{FormClose}]\n");
447: } else if (n.equals("input")) {
448: String type = e.getAttributeValue("type");
449: String name = e.getAttributeValue("name");
450: String value = e.getAttributeValue("value");
451: String checked = e.getAttributeValue("checked");
452:
453: m_out.print("[{FormInput");
454:
455: if (type != null) {
456: m_out.print(" type='" + type + "'");
457: }
458: if (name != null) {
459: // remove the "nbf_" that was prepended since new one will be generated again when the xhtml is rendered.
460: if (name.startsWith("nbf_")) {
461: name = name.substring(4, name.length());
462: }
463: m_out.print(" name='" + name + "'");
464: }
465: if (value != null && !value.equals("")) {
466: m_out.print(" value='" + value + "'");
467: }
468: if (checked != null) {
469: m_out.print(" checked='" + checked + "'");
470: }
471:
472: m_out.print("}]");
473:
474: print(e);
475: } else if (n.equals("textarea")) {
476: String name = e.getAttributeValue("name");
477: String rows = e.getAttributeValue("rows");
478: String cols = e.getAttributeValue("cols");
479:
480: m_out.print("[{FormTextarea");
481:
482: if (name != null) {
483: if (name.startsWith("nbf_")) {
484: name = name.substring(4, name.length());
485: }
486: m_out.print(" name='" + name + "'");
487: }
488: if (rows != null) {
489: m_out.print(" rows='" + rows + "'");
490: }
491: if (cols != null) {
492: m_out.print(" cols='" + cols + "'");
493: }
494:
495: m_out.print("}]");
496: print(e);
497: } else if (n.equals("select")) {
498: String name = e.getAttributeValue("name");
499:
500: m_out.print("[{FormSelect");
501:
502: if (name != null) {
503: if (name.startsWith("nbf_")) {
504: name = name.substring(4, name.length());
505: }
506: m_out.print(" name='" + name + "'");
507: }
508:
509: m_out.print(" value='");
510: print(e);
511: m_out.print("'}]");
512: } else if (n.equals("option")) {
513: // If this <option> element isn't the second child element within the parent <select>
514: // element, then we need to print a semicolon as a separator. (The first child element
515: // is expected to be a newline character which is at index of 0).
516: if (base.indexOf(e) != 1) {
517: m_out.print(";");
518: }
519:
520: Attribute selected = e.getAttribute("selected");
521: if (selected != null) {
522: m_out.print("*");
523: }
524:
525: String value = e.getAttributeValue("value");
526: if (value != null) {
527: m_out.print(value);
528: } else {
529: print(e);
530: }
531: } else {
532: print(e);
533: }
534: } else {
535: print(c);
536: }
537: }
538: }
539:
540: private void printImage(Element base) throws JDOMException {
541: Element child = (Element) XPath.selectSingleNode(base,
542: "TBODY/TR/TD/*");
543: if (child == null) {
544: child = base;
545: }
546: Element img;
547: String href;
548: Map map = new ForgetNullValuesLinkedHashMap();
549: if (child.getName().equals("A")) {
550: img = child.getChild("IMG");
551: href = child.getAttributeValue("href");
552: } else {
553: img = child;
554: href = null;
555: }
556: if (img == null) {
557: return;
558: }
559: String src = trimLink(img.getAttributeValue("src"));
560: if (src == null) {
561: return;
562: }
563: map.put("align", base.getAttributeValue("align"));
564: map.put("height", img.getAttributeValue("height"));
565: map.put("width", img.getAttributeValue("width"));
566: map.put("alt", img.getAttributeValue("alt"));
567: map.put("caption", emptyToNull(XPath.newInstance("CAPTION")
568: .valueOf(base)));
569: map.put("link", href);
570: map.put("border", img.getAttributeValue("border"));
571: map.put("style", base.getAttributeValue("style"));
572: if (map.size() > 0) {
573: m_out.print("[{Image src='" + src + "'");
574: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
575: Map.Entry entry = (Map.Entry) i.next();
576: if (!entry.getValue().equals("")) {
577: m_out.print(" " + entry.getKey() + "='"
578: + entry.getValue() + "'");
579: }
580: }
581: m_out.print("}]");
582: } else {
583: m_out.print("[" + src + "]");
584: }
585: }
586:
587: private String emptyToNull(String s) {
588: return s == null ? null
589: : (s.replaceAll("\\s", "").length() == 0 ? null : s);
590: }
591:
592: private String propsToStyleString(Map styleProps) {
593: StringBuffer style = new StringBuffer();
594: for (Iterator i = styleProps.entrySet().iterator(); i.hasNext();) {
595: Map.Entry entry = (Map.Entry) i.next();
596: style.append(" ").append(entry.getKey()).append(": ")
597: .append(entry.getValue()).append(";");
598: }
599: return style.toString();
600: }
601:
602: private boolean isIgnorableWikiMarkupLink(Element a) {
603: String ref = a.getAttributeValue("href");
604: String clazz = a.getAttributeValue("class");
605: return (ref != null && ref
606: .startsWith(m_config.getPageInfoJsp()))
607: || (clazz != null && clazz.trim().equalsIgnoreCase(
608: m_config.getOutlink()));
609: }
610:
611: /**
612: * Checks if the link points to an undefined page.
613: */
614: private boolean isUndefinedPageLink(Element a) {
615: String classVal = a.getAttributeValue("class");
616:
617: return classVal != null && classVal.equals("createpage");
618: }
619:
620: /**
621: * Returns a Map containing the valid augmented wiki link attributes.
622: */
623: private Map getAugmentedWikiLinkAttributes(Element a) {
624: Map attributesMap = new HashMap();
625:
626: String id = a.getAttributeValue("id");
627: if (id != null && !id.equals("")) {
628: attributesMap.put("id", id.replaceAll("'", "\""));
629: }
630:
631: String cssClass = a.getAttributeValue("class");
632: if (cssClass != null
633: && !cssClass.equals("")
634: && !cssClass
635: .matches("wikipage|createpage|external|interwiki|attachment")) {
636: attributesMap.put("class", cssClass.replaceAll("'", "\""));
637: }
638:
639: String style = a.getAttributeValue("style");
640: if (style != null && !style.equals("")) {
641: attributesMap.put("style", style.replaceAll("'", "\""));
642: }
643:
644: String title = a.getAttributeValue("title");
645: if (title != null && !title.equals("")) {
646: attributesMap.put("title", title.replaceAll("'", "\""));
647: }
648:
649: String lang = a.getAttributeValue("lang");
650: if (lang != null && !lang.equals("")) {
651: attributesMap.put("lang", lang.replaceAll("'", "\""));
652: }
653:
654: String dir = a.getAttributeValue("dir");
655: if (dir != null && !dir.equals("")) {
656: attributesMap.put("dir", dir.replaceAll("'", "\""));
657: }
658:
659: String charset = a.getAttributeValue("charset");
660: if (charset != null && !charset.equals("")) {
661: attributesMap.put("charset", charset.replaceAll("'", "\""));
662: }
663:
664: String type = a.getAttributeValue("type");
665: if (type != null && !type.equals("")) {
666: attributesMap.put("type", type.replaceAll("'", "\""));
667: }
668:
669: String hreflang = a.getAttributeValue("hreflang");
670: if (hreflang != null && !hreflang.equals("")) {
671: attributesMap.put("hreflang", hreflang
672: .replaceAll("'", "\""));
673: }
674:
675: String rel = a.getAttributeValue("rel");
676: if (rel != null && !rel.equals("")) {
677: attributesMap.put("rel", rel.replaceAll("'", "\""));
678: }
679:
680: String rev = a.getAttributeValue("rev");
681: if (rev != null && !rev.equals("")) {
682: attributesMap.put("rev", rev.replaceAll("'", "\""));
683: }
684:
685: String accesskey = a.getAttributeValue("accesskey");
686: if (accesskey != null && !accesskey.equals("")) {
687: attributesMap.put("accesskey", accesskey.replaceAll("'",
688: "\""));
689: }
690:
691: String tabindex = a.getAttributeValue("tabindex");
692: if (tabindex != null && !tabindex.equals("")) {
693: attributesMap.put("tabindex", tabindex
694: .replaceAll("'", "\""));
695: }
696:
697: String target = a.getAttributeValue("target");
698: if (target != null && !target.equals("")) {
699: attributesMap.put("target", target.replaceAll("'", "\""));
700: }
701:
702: return attributesMap;
703: }
704:
705: /**
706: * Converts the entries in the map to a string for use in a wiki link.
707: */
708: private String augmentedWikiLinkMapToString(Map attributesMap) {
709: StringBuffer sb = new StringBuffer();
710:
711: for (Iterator itr = attributesMap.entrySet().iterator(); itr
712: .hasNext();) {
713: Map.Entry entry = (Map.Entry) itr.next();
714: String attributeName = (String) entry.getKey();
715: String attributeValue = (String) entry.getValue();
716:
717: sb
718: .append(" " + attributeName + "='" + attributeValue
719: + "'");
720: }
721:
722: return sb.toString().trim();
723: }
724:
725: private Map getStylePropertiesLowerCase(Element base)
726: throws IOException {
727: String n = base.getName().toLowerCase();
728:
729: //"font-weight: bold; font-style: italic;"
730: String style = base.getAttributeValue("style");
731: if (style == null) {
732: style = "";
733: }
734:
735: if (n.equals("p") || n.equals("div")) {
736: String align = base.getAttributeValue("align");
737: if (align != null) {
738: // only add the value of the align attribute if the text-align style didn't already exist.
739: if (style.indexOf("text-align") == -1) {
740: style = style + ";text-align:" + align + ";";
741: }
742: }
743: }
744:
745: if (n.equals("font")) {
746: String color = base.getAttributeValue("color");
747: String face = base.getAttributeValue("face");
748: String size = base.getAttributeValue("size");
749: if (color != null) {
750: style = style + "color:" + color + ";";
751: }
752: if (face != null) {
753: style = style + "font-family:" + face + ";";
754: }
755: if (size != null) {
756: if (size.equals("1")) {
757: style = style + "font-size:xx-small;";
758: } else if (size.equals("2")) {
759: style = style + "font-size:x-small;";
760: } else if (size.equals("3")) {
761: style = style + "font-size:small;";
762: } else if (size.equals("4")) {
763: style = style + "font-size:medium;";
764: } else if (size.equals("5")) {
765: style = style + "font-size:large;";
766: } else if (size.equals("6")) {
767: style = style + "font-size:x-large;";
768: } else if (size.equals("7")) {
769: style = style + "font-size:xx-large;";
770: }
771: }
772: }
773:
774: if (style.equals("")) {
775: return null;
776: }
777:
778: style = style.replace(';', '\n').toLowerCase();
779: LinkedHashMap m = new LinkedHashMap();
780: new PersistentMapDecorator(m).load(new ByteArrayInputStream(
781: style.getBytes()));
782: return m;
783: }
784:
785: private String trimLink(String ref) {
786: if (ref == null) {
787: return null;
788: }
789: try {
790: ref = URLDecoder.decode(ref, UTF8);
791: ref = ref.trim();
792: if (ref.startsWith(m_config.getAttachPage())) {
793: ref = ref.substring(m_config.getAttachPage().length());
794: }
795: if (ref.startsWith(m_config.getWikiJspPage())) {
796: ref = ref.substring(m_config.getWikiJspPage().length());
797:
798: // Handle links with section anchors.
799: // For example, we need to translate the html string "TargetPage#section-TargetPage-Heading2"
800: // to this wiki string "TargetPage#Heading2".
801: ref = ref.replaceFirst(".+#section-(.+)-(.+)", "$1#$2");
802: }
803: if (ref.startsWith(m_config.getEditJspPage())) {
804: ref = ref.substring(m_config.getEditJspPage().length());
805: }
806: if (m_config.getPageName() != null) {
807: if (ref.startsWith(m_config.getPageName())) {
808: ref = ref
809: .substring(m_config.getPageName().length());
810: }
811: }
812: } catch (UnsupportedEncodingException e) {
813: // Shouldn't happen...
814: }
815: return ref;
816: }
817:
818: // FIXME: These should probably be better used with java.util.Stack
819:
820: static class LiStack {
821:
822: private StringBuffer m_li = new StringBuffer();
823:
824: public void push(String c) {
825: m_li.append(c);
826: }
827:
828: public void pop() {
829: m_li = m_li.deleteCharAt(m_li.length() - 1);
830: // m_li = m_li.substring( 0, m_li.length() - 1 );
831: }
832:
833: public String toString() {
834: return m_li.toString();
835: }
836:
837: }
838:
839: class PreStack {
840:
841: private int m_pre = 0;
842:
843: public boolean isPreMode() {
844: return m_pre > 0;
845: }
846:
847: public void push() {
848: m_pre++;
849: m_outTimmer.setWhitespaceTrimMode(!isPreMode());
850: }
851:
852: public void pop() {
853: m_pre--;
854: m_outTimmer.setWhitespaceTrimMode(!isPreMode());
855: }
856:
857: }
858:
859: }
|