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.render; 020 021import java.io.IOException; 022import java.io.StringWriter; 023import java.util.Iterator; 024 025import org.apache.wiki.WikiContext; 026import org.apache.wiki.htmltowiki.XHtmlToWikiConfig; 027import org.apache.wiki.parser.MarkupParser; 028import org.apache.wiki.parser.WikiDocument; 029import org.jdom2.Attribute; 030import org.jdom2.Element; 031import org.jdom2.output.Format; 032import org.jdom2.output.XMLOutputter; 033 034/** 035 * Implements a WikiRendered that outputs XHTML in a format that is suitable 036 * for use by a WYSIWYG XHTML editor. 037 * 038 * @since 2.5 039 */ 040public class WysiwygEditingRenderer 041 extends WikiRenderer 042{ 043 044 private static final String A_ELEMENT = "a"; 045 private static final String IMG_ELEMENT = "img"; 046// private static final String PRE_ELEMENT = "pre"; 047 private static final String CLASS_ATTRIBUTE = "class"; 048 private static final String HREF_ATTRIBUTE = "href"; 049 private static final String TITLE_ATTRIBUTE = "title"; 050 private static final String LINEBREAK = "\n"; 051 052 /** 053 * Creates a WYSIWYG editing renderer. 054 * 055 * @param context A WikiContext in which the rendering will take place. 056 * @param doc The WikiDocument which shall be rendered. 057 */ 058 public WysiwygEditingRenderer( WikiContext context, WikiDocument doc ) 059 { 060 super( context, doc ); 061 } 062 063 /* 064 * Recursively walk the XHTML DOM tree and manipulate specific elements to 065 * make them better for WYSIWYG editing. 066 */ 067 private void processChildren(Element baseElement) 068 { 069 for( Iterator< Element > itr = baseElement.getChildren().iterator(); itr.hasNext(); ) 070 { 071 Element element = itr.next(); 072 String elementName = element.getName().toLowerCase(); 073 Attribute classAttr = element.getAttribute( CLASS_ATTRIBUTE ); 074 075 if( elementName.equals( A_ELEMENT ) ) 076 { 077 if( classAttr != null ) 078 { 079 String classValue = classAttr.getValue(); 080 Attribute hrefAttr = element.getAttribute( HREF_ATTRIBUTE ); 081 082 XHtmlToWikiConfig wikiConfig = new XHtmlToWikiConfig( m_context ); 083 084 // Get the url for wiki page link - it's typically "Wiki.jsp?page=MyPage" 085 // or when using the ShortURLConstructor option, it's "wiki/MyPage" . 086 String wikiPageLinkUrl = wikiConfig.getWikiJspPage(); 087 String editPageLinkUrl = wikiConfig.getEditJspPage(); 088 089 //if( classValue.equals( WIKIPAGE ) 090 // || ( hrefAttr != null && hrefAttr.getValue().startsWith( wikiPageLinkUrl ) ) ) 091 if( //classValue.equals( WIKIPAGE ) && 092 ( hrefAttr != null ) 093 && ( hrefAttr.getValue().startsWith( wikiPageLinkUrl ) ) ) 094 { 095 // Remove the leading url string so that users will only see the 096 // wikipage's name when editing an existing wiki link. 097 // For example, change "Wiki.jsp?page=MyPage" to just "MyPage". 098 099 String newHref = hrefAttr.getValue().substring( wikiPageLinkUrl.length() ); 100 101 // Convert "This%20Pagename%20Has%20Spaces" to "This Pagename Has Spaces" 102 newHref = m_context.getEngine().decodeName( newHref ); 103 104 // Handle links with section anchors. 105 // For example, we need to translate the html string "TargetPage#section-TargetPage-Heading2" 106 // to this wiki string: "TargetPage#Heading2". 107 hrefAttr.setValue( newHref.replaceFirst( LINKS_SOURCE, LINKS_TRANSLATION ) ); 108 109 } 110 else if( //classValue.equals( EDITPAGE ) && 111 ( hrefAttr != null ) 112 && ( hrefAttr.getValue().startsWith( editPageLinkUrl ) ) ) 113 { 114 115 Attribute titleAttr = element.getAttribute( TITLE_ATTRIBUTE ); 116 if( titleAttr != null ) 117 { 118 // remove the title since we don't want to eventually save the default undefined page title. 119 titleAttr.detach(); 120 } 121 122 String newHref = hrefAttr.getValue().substring( editPageLinkUrl.length() ); 123 newHref = m_context.getEngine().decodeName( newHref ); 124 125 hrefAttr.setValue( newHref ); 126 } 127 128 else if( classValue.equals( MarkupParser.HASHLINK ) ) 129 { 130 itr.remove(); //remove element without disturbing the ongoing iteration 131 continue; //take next iteration of the for loop 132 } 133 } 134 } // end of check for "a" element 135 136 else if ( elementName.equals( IMG_ELEMENT ) ) 137 { 138 if( classAttr != null ) 139 { 140 String classValue = classAttr.getValue(); 141 142 if( classValue.equals( MarkupParser.OUTLINK ) ) 143 { 144 itr.remove(); //remove element without disturbing the ongoing iteration 145 continue; //take next iteration of the for loop 146 } 147 148 } 149 150 } 151 152 processChildren( element ); 153 } 154 } 155 156 /** 157 * {@inheritDoc} 158 */ 159 public String getString() 160 throws IOException 161 { 162 Element rootElement = m_document.getRootElement(); 163 processChildren( rootElement ); 164 165 m_document.setContext( m_context ); 166 167 CustomXMLOutputProcessor processor = new CustomXMLOutputProcessor(); 168 XMLOutputter output = new XMLOutputter(processor); 169 170 StringWriter out = new StringWriter(); 171 172 Format fmt = Format.getRawFormat(); 173 fmt.setExpandEmptyElements( false ); 174 fmt.setLineSeparator( LINEBREAK ); 175 176 output.setFormat( fmt ); 177 output.outputElementContent( m_document.getRootElement(), out ); 178 179 return out.toString(); 180 } 181}