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}