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