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