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}