This is an automated email from the ASF dual-hosted git repository. juanpablo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/jspwiki.git
commit b60ef16ed69f10634e6dca7a5515df1873b36f56 Author: Juan Pablo Santos RodrÃguez <[email protected]> AuthorDate: Sat Oct 16 17:59:54 2021 +0200 First stab at refactor so it'll be easier in the future to make it output other types of wiki syntaxes * Deleted ForgetNullValuesLinkedHashMap, used only in one place, it's clearer to explicitly put if value not empty * Deleted PersistentMapDecorator, used only in one place, Stream's api is clearer + avoid rethrowing meaningless IOException * Print methods.. renamed to translate.. * Extracted case statements on printChildren to their own decorator methods * Other parts of the class outputting wiki syntax extracted to their own de* Replaced privacorator methods te LiStack by plain java.util.Stack * Refactored private PreStack so it extends java.util.Stack * Moved getXPathElement method to XmlUtil, as a static method --- .../htmltowiki/ForgetNullValuesLinkedHashMap.java | 44 - .../wiki/htmltowiki/PersistentMapDecorator.java | 181 --- .../htmltowiki/XHtmlElementToWikiTranslator.java | 1516 +++++++++----------- .../main/java/org/apache/wiki/util/XmlUtil.java | 9 + .../java/org/apache/wiki/util/XmlUtilTest.java | 8 + 5 files changed, 723 insertions(+), 1035 deletions(-) diff --git a/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/ForgetNullValuesLinkedHashMap.java b/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/ForgetNullValuesLinkedHashMap.java deleted file mode 100644 index e5b1973..0000000 --- a/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/ForgetNullValuesLinkedHashMap.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ -package org.apache.wiki.htmltowiki; - -import java.util.LinkedHashMap; - -/** - * A LinkedHashMap that does not put null values into the map. - * - */ -public class ForgetNullValuesLinkedHashMap<K,V> extends LinkedHashMap<K,V> -{ - private static final long serialVersionUID = 0L; - - /** - * {@inheritDoc} - */ - @Override - public V put(final K key, final V value ) - { - if( value != null ) - { - return super.put( key, value ); - } - - return null; - } -} diff --git a/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/PersistentMapDecorator.java b/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/PersistentMapDecorator.java deleted file mode 100644 index 251e3d6..0000000 --- a/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/PersistentMapDecorator.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ -package org.apache.wiki.htmltowiki; - -import java.util.Collection; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -/** - * Adds the load / save - functionality known from the Properties - class to any - * Map implementation. - * - */ -public class PersistentMapDecorator extends Properties -{ - private static final long serialVersionUID = 0L; - - private final Map< Object, Object > m_delegate; - - /** - * Creates a new decorator for a given map. - * - * @param delegate The map to create a decorator for. - */ - public PersistentMapDecorator(final Map< Object, Object > delegate ) - { - m_delegate = delegate; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() - { - m_delegate.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsKey(final Object key ) - { - return m_delegate.containsKey( key ); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsValue(final Object value ) - { - return m_delegate.containsValue( value ); - } - - /** - * {@inheritDoc} - */ - @Override - public Set< Map.Entry< Object, Object > > entrySet() - { - return m_delegate.entrySet(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(final Object obj ) - { - return m_delegate.equals( obj ); - } - - /** - * {@inheritDoc} - */ - @Override - public Object get(final Object key ) - { - return m_delegate.get( key ); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() - { - return m_delegate.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEmpty() - { - return m_delegate.isEmpty(); - } - - /** - * {@inheritDoc} - */ - @Override - public Set< Object > keySet() - { - return m_delegate.keySet(); - } - - /** - * {@inheritDoc} - */ - @Override - public Object put(final Object arg0, final Object arg1 ) - { - return m_delegate.put( arg0, arg1 ); - } - - /** - * {@inheritDoc} - */ - @Override - public void putAll(final Map< ?, ? > arg0 ) - { - m_delegate.putAll( arg0 ); - } - - /** - * {@inheritDoc} - */ - @Override - public Object remove(final Object key ) - { - return m_delegate.remove( key ); - } - - /** - * {@inheritDoc} - */ - @Override - public int size() - { - return m_delegate.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() - { - return m_delegate.toString(); - } - - /** - * {@inheritDoc} - */ - @Override - public Collection< Object > values() - { - return m_delegate.values(); - } -} diff --git a/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/XHtmlElementToWikiTranslator.java b/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/XHtmlElementToWikiTranslator.java index c2d3bbf..78ffbf7 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/XHtmlElementToWikiTranslator.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/htmltowiki/XHtmlElementToWikiTranslator.java @@ -18,7 +18,9 @@ */ package org.apache.wiki.htmltowiki; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; +import org.apache.wiki.util.XmlUtil; import org.jdom2.Attribute; import org.jdom2.Content; import org.jdom2.Element; @@ -26,45 +28,39 @@ import org.jdom2.JDOMException; import org.jdom2.Text; import org.jdom2.xpath.XPathFactory; -import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; +import java.util.Stack; /** - * Converting XHtml to Wiki Markup. This is the class which does all of the heavy loading. - * + * Converting XHtml to Wiki Markup. This is the class which does all the heavy loading. */ -public class XHtmlElementToWikiTranslator -{ +public class XHtmlElementToWikiTranslator { - private final XHtmlToWikiConfig m_config; + private final XHtmlToWikiConfig config; - private final WhitespaceTrimWriter m_outTimmer; + private final WhitespaceTrimWriter outTrimmer = new WhitespaceTrimWriter(); - private final PrintWriter m_out; + private final PrintWriter out = new PrintWriter( outTrimmer ); - private final LiStack m_liStack = new LiStack(); + private final Stack< String > liStack = new Stack<>(); - private final PreStack m_preStack = new PreStack(); + private final Stack< String > preStack = new PreStack(); /** * Create a new translator using the default config. * * @param base The base element from which to start translating. - * @throws IOException If reading of the DOM tree fails. * @throws JDOMException If the DOM tree is faulty. */ - public XHtmlElementToWikiTranslator(final Element base ) throws IOException, JDOMException - { + public XHtmlElementToWikiTranslator(final Element base ) throws JDOMException { this( base, new XHtmlToWikiConfig() ); } @@ -73,941 +69,841 @@ public class XHtmlElementToWikiTranslator * * @param base The base element from which to start translating. * @param config The config to use. - * @throws IOException If reading of the DOM tree fails. * @throws JDOMException If the DOM tree is faulty. */ - public XHtmlElementToWikiTranslator(final Element base, final XHtmlToWikiConfig config ) throws IOException, JDOMException - { - this.m_config = config; - m_outTimmer = new WhitespaceTrimWriter(); - m_out = new PrintWriter( m_outTimmer ); - print( base ); + public XHtmlElementToWikiTranslator( final Element base, final XHtmlToWikiConfig config ) throws JDOMException { + this.config = config; + translate( base ); } /** - * FIXME: I have no idea what this does... + * Outputs parsed wikitext. * - * @return Something. + * @return parsed wikitext. */ - public String getWikiString() - { - return m_outTimmer.toString(); - } - - private void print( String s ) - { - s = StringEscapeUtils.unescapeHtml4( s ); - m_out.print( s ); - } - - private void print(final Object element ) throws IOException, JDOMException - { - if( element instanceof Text ) - { - final Text t = (Text)element; - String s = t.getText(); - if( m_preStack.isPreMode() ) - { - m_out.print( s ); - } - else - { - // remove all "line terminator" characters - s = s.replaceAll( "[\\r\\n\\f\\u0085\\u2028\\u2029]", "" ); - m_out.print( s ); - } - } - else if( element instanceof Element ) - { - final Element base = (Element)element; - String n = base.getName().toLowerCase(); - if( "imageplugin".equals( base.getAttributeValue( "class" ) ) ) - { - printImage( base ); - } - else if( "wikiform".equals( base.getAttributeValue( "class" ) ) ) - { + public String getWikiString() { + return outTrimmer.toString(); + } + + private void translate( final Content element ) throws JDOMException { + if( element instanceof Text ) { + decorateMarkupForText( ( Text )element ); + } else if( element instanceof Element ) { + final Element base = ( Element )element; + if( "imageplugin".equals( base.getAttributeValue( "class" ) ) ) { + translateImage( base ); + } else if( "wikiform".equals( base.getAttributeValue( "class" ) ) ) { // only print the children if the div's class="wikiform", but not the div itself. - printChildren( base ); + translateChildren( base ); + } else { + final ElementDecoratorData dto = buildElementDecoratorDataFrom( base ); + decorateMarkupForElementWith( dto ); } - else - { - boolean bold = false; - boolean italic = false; - boolean monospace = false; - String cssSpecial = null; - final String cssClass = base.getAttributeValue( "class" ); - - // accomodate a FCKeditor bug with Firefox: when a link is removed, it becomes <span class="wikipage">text</span>. - final boolean ignoredCssClass = cssClass != null && cssClass.matches( "wikipage|createpage|external|interwiki|attachment|inline-code" ); - - Map< Object, Object > styleProps = null; - - // Only get the styles if it's not a link element. Styles for link elements are - // handled as an AugmentedWikiLink instead. - if( !n.equals( "a" ) ) - { - styleProps = getStylePropertiesLowerCase( base ); - } - - if("inline-code".equals(cssClass)) - { - monospace = true; - } - - if( styleProps != null ) - { - final String weight = (String)styleProps.remove( "font-weight" ); - final String style = (String)styleProps.remove( "font-style" ); + } + } - if( n.equals( "p" ) ) - { - // change it so we can print out the css styles for <p> - n = "div"; - } + void decorateMarkupForString( final String s ) { + out.print( StringEscapeUtils.unescapeHtml4( s ) ); + } - italic = "oblique".equals( style ) || "italic".equals( style ); - bold = "bold".equals( weight ) || "bolder".equals( weight ); - if( !styleProps.isEmpty() ) - { - cssSpecial = propsToStyleString( styleProps ); - } - } - if( cssClass != null && !ignoredCssClass ) - { - if( n.equals( "div" ) ) - { - m_out.print( "\n%%" + cssClass + " \n" ); - } - else if( n.equals( "span" ) ) - { - m_out.print( "%%" + cssClass + " " ); - } - } - if( bold ) - { - m_out.print( "__" ); - } - if( italic ) - { - m_out.print( "''" ); - } - if( monospace ) - { - m_out.print( "{{{" ); - m_preStack.push(); - } - if( cssSpecial != null ) - { - if( n.equals( "div" ) ) - { - m_out.print( "\n%%(" + cssSpecial + " )\n" ); - } - else - { - m_out.print( "%%(" + cssSpecial + " )" ); - } - } - printChildren( base ); - if( cssSpecial != null ) - { - if( n.equals( "div" ) ) - { - m_out.print( "\n/%\n" ); - } - else - { - m_out.print( "/%" ); - } - } - if( monospace ) - { - m_preStack.pop(); - m_out.print( "}}}" ); - } - if( italic ) - { - m_out.print( "''" ); - } - if( bold ) - { - m_out.print( "__" ); - } - if( cssClass != null && !ignoredCssClass ) - { - if( n.equals( "div" ) ) - { - m_out.print( "\n/%\n" ); - } - else if( n.equals( "span" ) ) - { - m_out.print( "/%" ); - } - } - } + void decorateMarkupForText( final Text text ) { + String s = text.getText(); + if( preStack.isEmpty() ) { + // remove all "line terminator" characters + s = s.replaceAll( "[\\r\\n\\f\\u0085\\u2028\\u2029]", "" ); } + out.print( s ); } - private void printChildren(final Element base ) throws IOException, JDOMException - { - for(final Iterator< Content > i = base.getContent().iterator(); i.hasNext(); ) - { - final Content c = i.next(); - if( c instanceof Element ) - { - final Element e = (Element)c; - final String n = e.getName().toLowerCase(); - switch ( n ) { - case "h1": - m_out.print( "\n!!! " ); - print( e ); - m_out.println(); - break; - case "h2": - m_out.print( "\n!!! " ); - print( e ); - m_out.println(); - break; - case "h3": - m_out.print( "\n!! " ); - print( e ); - m_out.println(); - break; - case "h4": - m_out.print( "\n! " ); - print( e ); - m_out.println(); - break; - case "p": - if ( e.getContentSize() != 0 ) // we don't want to print empty elements: <p></p> - { - m_out.println(); - print( e ); - m_out.println(); - } - break; - case "br": - if ( m_preStack.isPreMode() ) { - m_out.println(); - } else { - final String parentElementName = base.getName().toLowerCase(); - - // - // To beautify the generated wiki markup, we print a newline character after a linebreak. - // It's only safe to do this when the parent element is a <p> or <div>; when the parent - // element is a table cell or list item, a newline character would break the markup. - // We also check that this isn't being done inside a plugin body. - // - if ( parentElementName.matches( "p|div" ) - && !base.getText().matches( "(?s).*\\[\\{.*\\}\\].*" ) ) { - m_out.print( " \\\\\n" ); - } else { - m_out.print( " \\\\" ); - } - } - print( e ); - break; - case "hr": - m_out.println(); - print( "----" ); - print( e ); - m_out.println(); - break; - case "table": - if ( !m_outTimmer.isCurrentlyOnLineBegin() ) { - m_out.println(); - } - print( e ); - break; - case "tr": - print( e ); - m_out.println(); - break; - case "td": - m_out.print( "| " ); - print( e ); - if ( !m_preStack.isPreMode() ) { - print( " " ); - } - break; - case "th": - m_out.print( "|| " ); - print( e ); - if ( !m_preStack.isPreMode() ) { - print( " " ); - } - break; - case "a": - if ( !isIgnorableWikiMarkupLink( e ) ) { - if ( e.getChild( "IMG" ) != null ) { - printImage( e ); - } else { - String ref = e.getAttributeValue( "href" ); - if ( ref == null ) { - if ( isUndefinedPageLink( e ) ) { - m_out.print( "[" ); - print( e ); - m_out.print( "]" ); - } else { - print( e ); - } - } else { - ref = trimLink( ref ); - if ( ref != null ) { - if ( ref.startsWith( "#" ) ) // This is a link to a footnote. - { - // convert "#ref-PageName-1" to just "1" - final String href = ref.replaceFirst( "#ref-.+-(\\d+)", "$1" ); - - // remove the brackets around "[1]" - final String textValue = e.getValue().substring( 1, ( e.getValue().length() - 1 ) ); - - if ( href.equals( textValue ) ) { // handles the simplest case. Example: [1] - print( e ); - } else { // handles the case where the link text is different from the href. Example: [something|1] - m_out.print( "[" + textValue + "|" + href + "]" ); - } - } else { - final Map<String, String> augmentedWikiLinkAttributes = getAugmentedWikiLinkAttributes( e ); - - m_out.print( "[" ); - print( e ); - if ( !e.getTextTrim().equalsIgnoreCase( ref ) ) { - m_out.print( "|" ); - print( ref ); - - if ( !augmentedWikiLinkAttributes.isEmpty() ) { - m_out.print( "|" ); - - final String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); - m_out.print( augmentedWikiLink ); - } - } else if ( !augmentedWikiLinkAttributes.isEmpty() ) { - // If the ref has the same value as the text and also if there - // are attributes, then just print: [ref|ref|attributes] . - m_out.print( "|" + ref + "|" ); - final String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); - m_out.print( augmentedWikiLink ); - } - - m_out.print( "]" ); - } - } - } - } - } - break; - case "b": - case "strong": - m_out.print( "__" ); - print( e ); - m_out.print( "__" ); - break; - case "i": - case "em": - case "address": - m_out.print( "''" ); - print( e ); - m_out.print( "''" ); - break; - case "u": - m_out.print( "%%( text-decoration:underline; )" ); - print( e ); - m_out.print( "/%" ); - break; - case "strike": - m_out.print( "%%strike " ); - print( e ); - m_out.print( "/%" ); - // NOTE: don't print a space before or after the double percents because that can break words into two. - // For example: %%(color:red)ABC%%%%(color:green)DEF%% is different from %%(color:red)ABC%% %%(color:green)DEF%% - break; - case "sup": - m_out.print( "%%sup " ); - print( e ); - m_out.print( "/%" ); - break; - case "sub": - m_out.print( "%%sub " ); - print( e ); - m_out.print( "/%" ); - break; - case "dl": - m_out.print( "\n" ); - print( e ); - - // print a newline after the definition list. If we don't, - // it may cause problems for the subsequent element. - m_out.print( "\n" ); - break; - case "dt": - m_out.print( ";" ); - print( e ); - break; - case "dd": - m_out.print( ":" ); - print( e ); - break; - case "ul": - m_out.println(); - m_liStack.push( "*" ); - print( e ); - m_liStack.pop(); - break; - case "ol": - m_out.println(); - m_liStack.push( "#" ); - print( e ); - m_liStack.pop(); - break; - case "li": - m_out.print( m_liStack + " " ); - print( e ); - - // The following line assumes that the XHTML has been "pretty-printed" - // (newlines separate child elements from their parents). - final boolean lastListItem = base.indexOf( e ) == ( base.getContentSize() - 2 ); - final boolean sublistItem = m_liStack.toString().length() > 1; - - // only print a newline if this <li> element is not the last item within a sublist. - if ( !sublistItem || !lastListItem ) { - m_out.println(); - } - break; - case "pre": - m_out.print( "\n{{{" ); // start JSPWiki "code blocks" on its own line - - m_preStack.push(); - print( e ); - m_preStack.pop(); - - // print a newline after the closing braces - // to avoid breaking any subsequent wiki markup that follows. - m_out.print( "}}}\n" ); - break; - case "code": - case "tt": - m_out.print( "{{" ); - m_preStack.push(); - print( e ); - m_preStack.pop(); - m_out.print( "}}" ); - // NOTE: don't print a newline after the closing brackets because if the Text is inside - // a table or list, it would break it if there was a subsequent row or list item. - break; - case "img": - if ( !isIgnorableWikiMarkupLink( e ) ) { - m_out.print( "[" ); - print( trimLink( e.getAttributeValue( "src" ) ) ); - m_out.print( "]" ); - } - break; - case "form": { - // remove the hidden input where name="formname" since a new one will be generated again when the xhtml is rendered. - final Element formName = getXPathElement( e, "INPUT[@name='formname']" ); - if ( formName != null ) { - formName.detach(); - } + ElementDecoratorData buildElementDecoratorDataFrom( final Element base ) { + String n = base.getName().toLowerCase(); + boolean bold = false; + boolean italic = false; + boolean monospace = false; + String cssSpecial = null; + final String cssClass = base.getAttributeValue( "class" ); - final String name = e.getAttributeValue( "name" ); + // accomodate a FCKeditor bug with Firefox: when a link is removed, it becomes <span class="wikipage">text</span>. + final boolean ignoredCssClass = cssClass != null && cssClass.matches( "wikipage|createpage|external|interwiki|attachment|inline-code" ); - m_out.print( "\n[{FormOpen" ); + Map< Object, Object > styleProps = null; - if ( name != null ) { - m_out.print( " form='" + name + "'" ); - } + // Only get the styles if it's not a link element. Styles for link elements are handled as an AugmentedWikiLink instead. + if( !n.equals( "a" ) ) { + styleProps = getStylePropertiesLowerCase( base ); + } - m_out.print( "}]\n" ); + if( "inline-code".equals( cssClass ) ) { + monospace = true; + } - print( e ); - m_out.print( "\n[{FormClose}]\n" ); - break; - } - case "input": { - final String type = e.getAttributeValue( "type" ); - String name = e.getAttributeValue( "name" ); - final String value = e.getAttributeValue( "value" ); - final String checked = e.getAttributeValue( "checked" ); + if( styleProps != null ) { + final String weight = ( String ) styleProps.remove( "font-weight" ); + final String style = ( String ) styleProps.remove( "font-style" ); - m_out.print( "[{FormInput" ); + if ( n.equals( "p" ) ) { + // change it, so we can print out the css styles for <p> + n = "div"; + } - if ( type != null ) { - m_out.print( " type='" + type + "'" ); - } - if ( name != null ) { - // remove the "nbf_" that was prepended since new one will be generated again when the xhtml is rendered. - if ( name.startsWith( "nbf_" ) ) { - name = name.substring( 4, name.length() ); - } - m_out.print( " name='" + name + "'" ); - } - if ( value != null && !value.equals( "" ) ) { - m_out.print( " value='" + value + "'" ); - } - if ( checked != null ) { - m_out.print( " checked='" + checked + "'" ); - } + italic = "oblique".equals( style ) || "italic".equals( style ); + bold = "bold".equals( weight ) || "bolder".equals( weight ); + if ( !styleProps.isEmpty() ) { + cssSpecial = propsToStyleString( styleProps ); + } + } - m_out.print( "}]" ); + final ElementDecoratorData dto = new ElementDecoratorData(); + dto.base = base; + dto.bold = bold; + dto.cssClass = cssClass; + dto.cssSpecial = cssSpecial; + dto.htmlBase = n; + dto.ignoredCssClass = ignoredCssClass; + dto.italic = italic; + dto.monospace = monospace; + return dto; + } - print( e ); - break; - } - case "textarea": { - String name = e.getAttributeValue( "name" ); - final String rows = e.getAttributeValue( "rows" ); - final String cols = e.getAttributeValue( "cols" ); + private Map< Object, Object > getStylePropertiesLowerCase( final Element base ) { + final String n = base.getName().toLowerCase(); - m_out.print( "[{FormTextarea" ); + // "font-weight: bold; font-style: italic;" + String style = base.getAttributeValue( "style" ); + if( style == null ) { + style = ""; + } - if ( name != null ) { - if ( name.startsWith( "nbf_" ) ) { - name = name.substring( 4, name.length() ); - } - m_out.print( " name='" + name + "'" ); - } - if ( rows != null ) { - m_out.print( " rows='" + rows + "'" ); - } - if ( cols != null ) { - m_out.print( " cols='" + cols + "'" ); - } + if( n.equals( "p" ) || n.equals( "div" ) ) { + final String align = base.getAttributeValue( "align" ); + if( align != null ) { + // only add the value of the align attribute if the text-align style didn't already exist. + if( !style.contains( "text-align" ) ) { + style += ";text-align:" + align + ";"; + } + } + } - m_out.print( "}]" ); - print( e ); - break; - } - case "select": { - String name = e.getAttributeValue( "name" ); + if( n.equals( "font" ) ) { + final String color = base.getAttributeValue( "color" ); + final String face = base.getAttributeValue( "face" ); + final String size = base.getAttributeValue( "size" ); + if( color != null ) { + style = style + "color:" + color + ";"; + } + if( face != null ) { + style = style + "font-family:" + face + ";"; + } + if( size != null ) { + switch ( size ) { + case "1": style += "font-size:xx-small;"; break; + case "2": style += "font-size:x-small;"; break; + case "3": style += "font-size:small;"; break; + case "4": style += "font-size:medium;"; break; + case "5": style += "font-size:large;"; break; + case "6": style += "font-size:x-large;"; break; + case "7": style += "font-size:xx-large;"; break; + } + } + } - m_out.print( "[{FormSelect" ); + if( style.equals( "" ) ) { + return null; + } - if ( name != null ) { - if ( name.startsWith( "nbf_" ) ) { - name = name.substring( 4, name.length() ); - } - m_out.print( " name='" + name + "'" ); - } + final Map< Object, Object > m = new LinkedHashMap<>(); + Arrays.stream( style.toLowerCase().split( ";" ) ) + .filter( StringUtils::isNotEmpty ) + .forEach( prop -> m.put( prop.split( ":" )[ 0 ].trim(), prop.split( ":" )[ 1 ].trim() ) ); + return m; + } - m_out.print( " value='" ); - print( e ); - m_out.print( "'}]" ); - break; - } - case "option": { - // If this <option> element isn't the second child element within the parent <select> - // element, then we need to print a semicolon as a separator. (The first child element - // is expected to be a newline character which is at index of 0). - if ( base.indexOf( e ) != 1 ) { - m_out.print( ";" ); - } + private String propsToStyleString( final Map< Object, Object > styleProps ) { + final StringBuilder style = new StringBuilder(); + for( final Map.Entry< Object, Object > entry : styleProps.entrySet() ) { + style.append( " " ).append( entry.getKey() ).append( ": " ).append( entry.getValue() ).append( ";" ); + } + return style.toString(); + } - final Attribute selected = e.getAttribute( "selected" ); - if ( selected != null ) { - m_out.print( "*" ); - } + void decorateMarkupForElementWith( final ElementDecoratorData dto ) throws JDOMException { + decorateMarkupForCssClass( dto ); + } - final String value = e.getAttributeValue( "value" ); - if ( value != null ) { - m_out.print( value ); - } else { - print( e ); - } - break; - } - default: - print( e ); - break; - } + void decorateMarkupForCssClass( final ElementDecoratorData dto ) throws JDOMException { + if( dto.cssClass != null && !dto.ignoredCssClass ) { + if ( dto.htmlBase.equals( "div" ) ) { + out.print( "\n%%" + dto.cssClass + " \n" ); + } else if ( dto.htmlBase.equals( "span" ) ) { + out.print( "%%" + dto.cssClass + " " ); } - else - { - print( c ); + } + decorateMarkupForBold( dto ); + if( dto.cssClass != null && !dto.ignoredCssClass ) { + if ( dto.htmlBase.equals( "div" ) ) { + out.print( "\n/%\n" ); + } else if ( dto.htmlBase.equals( "span" ) ) { + out.print( "/%" ); } } } - private void printImage(final Element base ) - { - Element child = getXPathElement( base, "TBODY/TR/TD/*" ); - if( child == null ) - { - child = base; + void decorateMarkupForBold( final ElementDecoratorData dto ) throws JDOMException { + if( dto.bold ) { + out.print( "__" ); } - final Element img; - final String href; - final Map<Object,Object> map = new ForgetNullValuesLinkedHashMap<>(); - if( child.getName().equals( "A" ) ) - { - img = child.getChild( "IMG" ); - href = child.getAttributeValue( "href" ); + decorateMarkupForItalic( dto ); + if( dto.bold ) { + out.print( "__" ); } - else - { - img = child; - href = null; + } + + void decorateMarkupForItalic( final ElementDecoratorData dto ) throws JDOMException { + if( dto.italic ) { + out.print( "''" ); } - if( img == null ) - { - return; + decorateMarkupForMonospace( dto ); + if( dto.italic ) { + out.print( "''" ); } - final String src = trimLink( img.getAttributeValue( "src" ) ); - if( src == null ) - { - return; + } + + void decorateMarkupForMonospace( final ElementDecoratorData dto ) throws JDOMException { + if( dto.monospace ) { + out.print( "{{{" ); + preStack.push( "{{{" ); + } + decorateMarkupForCssSpecial( dto ); + if( dto.monospace ) { + preStack.pop(); + out.print( "}}}" ); + } + } + + void decorateMarkupForCssSpecial( final ElementDecoratorData dto ) throws JDOMException { + if( dto.cssSpecial != null ) { + if ( dto.htmlBase.equals( "div" ) ) { + out.print( "\n%%(" + dto.cssSpecial + " )\n" ); + } else { + out.print( "%%(" + dto.cssSpecial + " )" ); + } + } + translateChildren( dto.base ); + if( dto.cssSpecial != null ) { + if ( dto.htmlBase.equals( "div" ) ) { + out.print( "\n/%\n" ); + } else { + out.print( "/%" ); + } } - map.put( "align", base.getAttributeValue( "align" ) ); - map.put( "height", img.getAttributeValue( "height" ) ); - map.put( "width", img.getAttributeValue( "width" ) ); - map.put( "alt", img.getAttributeValue( "alt" ) ); - map.put( "caption", emptyToNull( ( Element )XPathFactory.instance().compile( "CAPTION" ).evaluateFirst( base ) ) ); - map.put( "link", href ); - map.put( "border", img.getAttributeValue( "border" ) ); - map.put( "style", base.getAttributeValue( "style" ) ); - if( map.size() > 0 ) - { - m_out.print( "[{Image src='" + src + "'" ); - for(final Iterator i = map.entrySet().iterator(); i.hasNext(); ) - { - final Map.Entry entry = (Map.Entry)i.next(); - if( !entry.getValue().equals( "" ) ) - { - m_out.print( " " + entry.getKey() + "='" + entry.getValue() + "'" ); + } + + private void translateChildren( final Element base ) throws JDOMException { + for( final Content c : base.getContent() ) { + if ( c instanceof Element ) { + final Element e = ( Element )c; + final String n = e.getName().toLowerCase(); + switch( n ) { + case "h1": decorateMarkupForH1( e ); break; + case "h2": decorateMarkupForH2( e ); break; + case "h3": decorateMarkupForH3( e ); break; + case "h4": decorateMarkupForH4( e ); break; + case "p": decorateMarkupForP( e ); break; + case "br": decorateMarkupForBR( base, e ); break; + case "hr": decorateMarkupForHR( e ); break; + case "table": decorateMarkupForTable( e ); break; + case "tr": decorateMarkupForTR( e ); break; + case "td": decorateMarkupForTD( e ); break; + case "th": decorateMarkupForTH( e ); break; + case "a": decorateMarkupForA( e ); break; + case "b": + case "strong": decorateMarkupForStrong( e ); break; + case "i": + case "em": + case "address": decorateMarkupForEM( e ); break; + case "u": decorateMarkupForUnderline( e ); break; + case "strike": decorateMarkupForStrike( e ); break; + case "sup": decorateMarkupForSup( e ); break; + case "sub": decorateMarkupForSub( e ); break; + case "dl": decorateMarkupForDL( e ); break; + case "dt": decorateMarkupForDT( e ); break; + case "dd": decorateMarkupForDD( e ); break; + case "ul": decorateMarkupForUL( e ); break; + case "ol": decorateMarkupForOL( e ); break; + case "li": decorateMarkupForLI( base, e ); break; + case "pre": decorateMarkupForPre( e ); break; + case "code": + case "tt": decorateMarkupForCode( e ); break; + case "img": decorateMarkupForImg( e ); break; + case "form": decorateMarkupForForm( e ); break; + case "input": decorateMarkupForInput( e ); break; + case "textarea": decorateMarkupForTextarea( e ); break; + case "select": decorateMarkupForSelect( e ); break; + case "option": decorateMarkupForOption( base, e ); break; + default: translate( e ); break; } + } else { + translate( c ); } - m_out.print( "}]" ); } - else - { - m_out.print( "[" + src + "]" ); + } + + void decorateMarkupForH1( final Element e ) throws JDOMException { + out.print( "\n!!! " ); + translate( e ); + out.println(); + } + + void decorateMarkupForH2( final Element e ) throws JDOMException { + out.print( "\n!!! " ); + translate( e ); + out.println(); + } + + void decorateMarkupForH3( final Element e ) throws JDOMException { + out.print( "\n!! " ); + translate( e ); + out.println(); + } + + void decorateMarkupForH4( final Element e ) throws JDOMException { + out.print( "\n! " ); + translate( e ); + out.println(); + } + + void decorateMarkupForP( final Element e ) throws JDOMException { + if ( e.getContentSize() != 0 ) { // we don't want to print empty elements: <p></p> + out.println(); + translate( e ); + out.println(); } } - Element getXPathElement( final Element base, final String expression ) { - final List< ? > nodes = XPathFactory.instance().compile( expression ).evaluate( base ); - if( nodes == null || nodes.size() == 0 ) { - return null; + void decorateMarkupForHR( final Element e ) throws JDOMException { + out.println(); + decorateMarkupForString( "----" ); + translate( e ); + out.println(); + } + + void decorateMarkupForBR( final Element base, final Element e ) throws JDOMException { + if ( !preStack.isEmpty() ) { + out.println(); } else { - return ( Element )nodes.get( 0 ); + final String parentElementName = base.getName().toLowerCase(); + + // To beautify the generated wiki markup, we print a newline character after a linebreak. + // It's only safe to do this when the parent element is a <p> or <div>; when the parent + // element is a table cell or list item, a newline character would break the markup. + // We also check that this isn't being done inside a plugin body. + if ( parentElementName.matches( "p|div" ) && !base.getText().matches( "(?s).*\\[\\{.*\\}\\].*" ) ) { + out.print( " \\\\\n" ); + } else { + out.print( " \\\\" ); + } } + translate( e ); } - private String emptyToNull( final Element e ) { - if( e == null ) { - return null; + void decorateMarkupForTable( final Element e ) throws JDOMException { + if ( !outTrimmer.isCurrentlyOnLineBegin() ) { + out.println(); } - final String s = e.getText(); - return s == null ? null : ( s.replaceAll( "\\s", "" ).isEmpty() ? null : s ); + translate( e ); } - private String propsToStyleString( final Map< Object, Object > styleProps ) { - final StringBuilder style = new StringBuilder(); - for(final Iterator< Map.Entry< Object, Object > > i = styleProps.entrySet().iterator(); i.hasNext(); ) { - final Map.Entry< Object, Object > entry = i.next(); - style.append( " " ).append( entry.getKey() ).append( ": " ).append( entry.getValue() ).append( ";" ); + void decorateMarkupForTR( final Element e ) throws JDOMException { + translate( e ); + out.println(); + } + + void decorateMarkupForTD( final Element e ) throws JDOMException { + out.print( "| " ); + translate( e ); + if( preStack.isEmpty() ) { + decorateMarkupForString( " " ); } - return style.toString(); } - private boolean isIgnorableWikiMarkupLink( final Element a ) { - final String ref = a.getAttributeValue( "href" ); - final String clazz = a.getAttributeValue( "class" ); - return ( ref != null && ref.startsWith( m_config.getPageInfoJsp() ) ) - || ( clazz != null && clazz.trim().equalsIgnoreCase( m_config.getOutlink() ) ); + void decorateMarkupForTH( final Element e ) throws JDOMException { + out.print( "|| " ); + translate( e ); + if( preStack.isEmpty() ) { + decorateMarkupForString( " " ); + } + } + + void decorateMarkupForA( final Element e ) throws JDOMException { + if( isNotIgnorableWikiMarkupLink( e ) ) { + if( e.getChild( "IMG" ) != null ) { + translateImage( e ); + } else { + String ref = e.getAttributeValue( "href" ); + if ( ref == null ) { + if ( isUndefinedPageLink( e ) ) { + out.print( "[" ); + translate( e ); + out.print( "]" ); + } else { + translate( e ); + } + } else { + ref = trimLink( ref ); + if ( ref != null ) { + if ( ref.startsWith( "#" ) ) { // This is a link to a footnote. + // convert "#ref-PageName-1" to just "1" + final String href = ref.replaceFirst( "#ref-.+-(\\d+)", "$1" ); + + // remove the brackets around "[1]" + final String textValue = e.getValue().substring( 1, ( e.getValue().length() - 1 ) ); + + if ( href.equals( textValue ) ) { // handles the simplest case. Example: [1] + translate( e ); + } else { // handles the case where the link text is different from the href. Example: [something|1] + out.print( "[" + textValue + "|" + href + "]" ); + } + } else { + final Map< String, String > augmentedWikiLinkAttributes = getAugmentedWikiLinkAttributes( e ); + + out.print( "[" ); + translate( e ); + if ( !e.getTextTrim().equalsIgnoreCase( ref ) ) { + out.print( "|" ); + decorateMarkupForString( ref ); + + if ( !augmentedWikiLinkAttributes.isEmpty() ) { + out.print( "|" ); + + final String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); + out.print( augmentedWikiLink ); + } + } else if ( !augmentedWikiLinkAttributes.isEmpty() ) { + // If the ref has the same value as the text and also if there + // are attributes, then just print: [ref|ref|attributes] . + out.print( "|" + ref + "|" ); + final String augmentedWikiLink = augmentedWikiLinkMapToString( augmentedWikiLinkAttributes ); + out.print( augmentedWikiLink ); + } + + out.print( "]" ); + } + } + } + } + } } /** * Checks if the link points to an undefined page. */ - private boolean isUndefinedPageLink(final Element a) - { + private boolean isUndefinedPageLink( final Element a ) { final String classVal = a.getAttributeValue( "class" ); - return "createpage".equals( classVal ); } /** * Returns a Map containing the valid augmented wiki link attributes. */ - private Map<String,String> getAugmentedWikiLinkAttributes(final Element a ) - { - final Map<String,String> attributesMap = new HashMap<>(); - - final String id = a.getAttributeValue( "id" ); - if( id != null && !id.equals( "" ) ) - { - attributesMap.put( "id", id.replaceAll( "'", "\"" ) ); - } - + private Map< String, String > getAugmentedWikiLinkAttributes( final Element a ) { + final Map< String, String > attributesMap = new HashMap<>(); final String cssClass = a.getAttributeValue( "class" ); - if( cssClass != null && !cssClass.equals( "" ) - && !cssClass.matches( "wikipage|createpage|external|interwiki|attachment" ) ) - { + if( StringUtils.isNotEmpty( cssClass ) + && !cssClass.matches( "wikipage|createpage|external|interwiki|attachment" ) ) { attributesMap.put( "class", cssClass.replaceAll( "'", "\"" ) ); } + addAttributeIfPresent( a, attributesMap, "accesskey" ); + addAttributeIfPresent( a, attributesMap, "charset" ); + addAttributeIfPresent( a, attributesMap, "dir" ); + addAttributeIfPresent( a, attributesMap, "hreflang" ); + addAttributeIfPresent( a, attributesMap, "id" ); + addAttributeIfPresent( a, attributesMap, "lang" ); + addAttributeIfPresent( a, attributesMap, "rel" ); + addAttributeIfPresent( a, attributesMap, "rev" ); + addAttributeIfPresent( a, attributesMap, "style" ); + addAttributeIfPresent( a, attributesMap, "tabindex" ); + addAttributeIfPresent( a, attributesMap, "target" ); + addAttributeIfPresent( a, attributesMap, "title" ); + addAttributeIfPresent( a, attributesMap, "type" ); + return attributesMap; + } - final String style = a.getAttributeValue( "style" ); - if( style != null && !style.equals( "" ) ) - { - attributesMap.put( "style", style.replaceAll( "'", "\"" ) ); + private void addAttributeIfPresent( final Element a, final Map< String, String > attributesMap, final String attribute ) { + final String attr = a.getAttributeValue( attribute ); + if( StringUtils.isNotEmpty( attr ) ) { + attributesMap.put( attribute, attr.replaceAll( "'", "\"" ) ); } + } - final String title = a.getAttributeValue( "title" ); - if( title != null && !title.equals( "" ) ) - { - attributesMap.put( "title", title.replaceAll( "'", "\"" ) ); - } + /** + * Converts the entries in the map to a string for use in a wiki link. + */ + private String augmentedWikiLinkMapToString( final Map< String, String > attributesMap ) { + final StringBuilder sb = new StringBuilder(); + for( final Map.Entry< String, String > entry : attributesMap.entrySet() ) { + final String attributeName = entry.getKey(); + final String attributeValue = entry.getValue(); - final String lang = a.getAttributeValue( "lang" ); - if( lang != null && !lang.equals( "" ) ) - { - attributesMap.put( "lang", lang.replaceAll( "'", "\"" ) ); + sb.append( " " ).append( attributeName ).append( "='" ).append( attributeValue ).append( "'" ); } - final String dir = a.getAttributeValue( "dir" ); - if( dir != null && !dir.equals( "" ) ) - { - attributesMap.put( "dir", dir.replaceAll( "'", "\"" ) ); - } + return sb.toString().trim(); + } - final String charset = a.getAttributeValue( "charset" ); - if( charset != null && !charset.equals("") ) - { - attributesMap.put( "charset", charset.replaceAll( "'", "\"" ) ); - } + void decorateMarkupForStrong( final Element e ) throws JDOMException { + out.print( "__" ); + translate( e ); + out.print( "__" ); + } - final String type = a.getAttributeValue( "type" ); - if( type != null && !type.equals( "" ) ) - { - attributesMap.put( "type", type.replaceAll( "'", "\"" ) ); - } + void decorateMarkupForEM( final Element e ) throws JDOMException { + out.print( "''" ); + translate( e ); + out.print( "''" ); + } + + void decorateMarkupForUnderline( final Element e ) throws JDOMException { + out.print( "%%( text-decoration:underline; )" ); + translate( e ); + out.print( "/%" ); + } + + void decorateMarkupForStrike( final Element e ) throws JDOMException { + out.print( "%%strike " ); + translate( e ); + out.print( "/%" ); + // NOTE: don't print a space before or after the double percents because that can break words into two. + // For example: %%(color:red)ABC%%%%(color:green)DEF%% is different from %%(color:red)ABC%% %%(color:green)DEF%% + } + + void decorateMarkupForSup( final Element e ) throws JDOMException { + out.print( "%%sup " ); + translate( e ); + out.print( "/%" ); + } + + void decorateMarkupForSub( final Element e ) throws JDOMException { + out.print( "%%sub " ); + translate( e ); + out.print( "/%" ); + } + + void decorateMarkupForDL( final Element e ) throws JDOMException { + out.print( "\n" ); + translate( e ); + + // print a newline after the definition list. If we don't, + // it may cause problems for the subsequent element. + out.print( "\n" ); + } + + void decorateMarkupForDT( final Element e ) throws JDOMException { + out.print( ";" ); + translate( e ); + } + + void decorateMarkupForDD( final Element e ) throws JDOMException { + out.print( ":" ); + translate( e ); + } + + void decorateMarkupForUL( final Element e ) throws JDOMException { + out.println(); + liStack.push( "*" ); + translate( e ); + liStack.pop(); + } + + void decorateMarkupForOL( final Element e ) throws JDOMException { + out.println(); + liStack.push( "#" ); + translate( e ); + liStack.pop(); + } - final String hreflang = a.getAttributeValue( "hreflang" ); - if( hreflang != null && !hreflang.equals( "" ) ) - { - attributesMap.put( "hreflang", hreflang.replaceAll( "'", "\"" ) ); + void decorateMarkupForLI( final Element base, final Element e ) throws JDOMException { + out.print( String.join( "", liStack ) + " " ); + translate( e ); + + // The following line assumes that the XHTML has been "pretty-printed" + // (newlines separate child elements from their parents). + final boolean lastListItem = base.indexOf( e ) == ( base.getContentSize() - 2 ); + final boolean sublistItem = liStack.size() > 1; + + // only print a newline if this <li> element is not the last item within a sublist. + if ( !sublistItem || !lastListItem ) { + out.println(); } + } + + void decorateMarkupForPre( final Element e ) throws JDOMException { + out.print( "\n{{{" ); // start JSPWiki "code blocks" on its own line + + preStack.push( "\n{{{" ); + translate( e ); + preStack.pop(); - final String rel = a.getAttributeValue( "rel" ); - if( rel != null && !rel.equals( "" ) ) - { - attributesMap.put( "rel", rel.replaceAll( "'", "\"" ) ); + // print a newline after the closing braces to avoid breaking any subsequent wiki markup that follows. + out.print( "}}}\n" ); + } + + void decorateMarkupForCode( final Element e ) throws JDOMException { + out.print( "{{" ); + preStack.push( "{{" ); + translate( e ); + preStack.pop(); + out.print( "}}" ); + // NOTE: don't print a newline after the closing brackets because if the Text is inside + // a table or list, it would break it if there was a subsequent row or list item. + } + + void decorateMarkupForImg( final Element e ) { + if( isNotIgnorableWikiMarkupLink( e ) ) { + out.print( "[" ); + decorateMarkupForString( trimLink( e.getAttributeValue( "src" ) ) ); + out.print( "]" ); } + } - final String rev = a.getAttributeValue( "rev" ); - if( rev != null && !rev.equals( "" ) ) - { - attributesMap.put( "rev", rev.replaceAll( "'", "\"" ) ); + void decorateMarkupForForm( final Element e ) throws JDOMException { + // remove the hidden input where name="formname" since a new one will be generated again when the xhtml is rendered. + final Element formName = XmlUtil.getXPathElement( e, "INPUT[@name='formname']" ); + if ( formName != null ) { + formName.detach(); } - final String accesskey = a.getAttributeValue( "accesskey" ); - if( accesskey != null && !accesskey.equals( "" ) ) - { - attributesMap.put( "accesskey", accesskey.replaceAll( "'", "\"" ) ); + final String name = e.getAttributeValue( "name" ); + + out.print( "\n[{FormOpen" ); + + if( name != null ) { + out.print( " form='" + name + "'" ); } - final String tabindex = a.getAttributeValue( "tabindex" ); - if( tabindex != null && !tabindex.equals( "" ) ) - { - attributesMap.put( "tabindex", tabindex.replaceAll( "'", "\"" ) ); + out.print( "}]\n" ); + + translate( e ); + out.print( "\n[{FormClose}]\n" ); + } + + void decorateMarkupForInput( final Element e ) throws JDOMException { + final String type = e.getAttributeValue( "type" ); + String name = e.getAttributeValue( "name" ); + final String value = e.getAttributeValue( "value" ); + final String checked = e.getAttributeValue( "checked" ); + + out.print( "[{FormInput" ); + + if ( type != null ) { + out.print( " type='" + type + "'" ); + } + if ( name != null ) { + // remove the "nbf_" that was prepended since new one will be generated again when the xhtml is rendered. + if ( name.startsWith( "nbf_" ) ) { + name = name.substring( 4 ); + } + out.print( " name='" + name + "'" ); + } + if ( value != null && !value.equals( "" ) ) { + out.print( " value='" + value + "'" ); } + if ( checked != null ) { + out.print( " checked='" + checked + "'" ); + } + + out.print( "}]" ); + + translate( e ); + } + + void decorateMarkupForTextarea( final Element e ) throws JDOMException { + String name = e.getAttributeValue( "name" ); + final String rows = e.getAttributeValue( "rows" ); + final String cols = e.getAttributeValue( "cols" ); - final String target = a.getAttributeValue( "target" ); - if( target != null && !target.equals( "" ) ) - { - attributesMap.put( "target", target.replaceAll( "'", "\"" ) ); + out.print( "[{FormTextarea" ); + + if ( name != null ) { + if ( name.startsWith( "nbf_" ) ) { + name = name.substring( 4 ); + } + out.print( " name='" + name + "'" ); + } + if ( rows != null ) { + out.print( " rows='" + rows + "'" ); + } + if ( cols != null ) { + out.print( " cols='" + cols + "'" ); } - return attributesMap; + out.print( "}]" ); + translate( e ); } - /** - * Converts the entries in the map to a string for use in a wiki link. - */ - private String augmentedWikiLinkMapToString(final Map attributesMap ) - { - final StringBuilder sb = new StringBuilder(); + void decorateMarkupForSelect( final Element e ) throws JDOMException { + String name = e.getAttributeValue( "name" ); - for (final Iterator itr = attributesMap.entrySet().iterator(); itr.hasNext(); ) - { - final Map.Entry entry = (Map.Entry)itr.next(); - final String attributeName = (String)entry.getKey(); - final String attributeValue = (String)entry.getValue(); + out.print( "[{FormSelect" ); - sb.append( " " + attributeName + "='" + attributeValue + "'" ); + if ( name != null ) { + if ( name.startsWith( "nbf_" ) ) { + name = name.substring( 4 ); + } + out.print( " name='" + name + "'" ); } - return sb.toString().trim(); + out.print( " value='" ); + translate( e ); + out.print( "'}]" ); } - private Map< Object, Object > getStylePropertiesLowerCase(final Element base ) throws IOException - { - final String n = base.getName().toLowerCase(); + void decorateMarkupForOption( final Element base, final Element e ) throws JDOMException { + // If this <option> element isn't the second child element within the parent <select> + // element, then we need to print a semicolon as a separator. (The first child element + // is expected to be a newline character which is at index of 0). + if ( base.indexOf( e ) != 1 ) { + out.print( ";" ); + } - //"font-weight: bold; font-style: italic;" - String style = base.getAttributeValue( "style" ); - if( style == null ) - { - style = ""; + final Attribute selected = e.getAttribute( "selected" ); + if ( selected != null ) { + out.print( "*" ); } - if( n.equals( "p" ) || n.equals( "div" ) ) - { - final String align = base.getAttributeValue( "align" ); - if( align != null ) - { - // only add the value of the align attribute if the text-align style didn't already exist. - if( style.indexOf( "text-align" ) == -1 ) - { - style = style + ";text-align:" + align + ";"; - } - } + final String value = e.getAttributeValue( "value" ); + if ( value != null ) { + out.print( value ); + } else { + translate( e ); } + } + private void translateImage( final Element base ) { + Element child = XmlUtil.getXPathElement( base, "TBODY/TR/TD/*" ); + if( child == null ) { + child = base; + } + final Element img; + final String href; + if( child.getName().equals( "A" ) ) { + img = child.getChild( "IMG" ); + href = child.getAttributeValue( "href" ); + } else { + img = child; + href = null; + } + if( img == null ) { + return; + } + final String src = trimLink( img.getAttributeValue( "src" ) ); + if( src == null ) { + return; + } + final Map< String, Object > imageAttrs = new LinkedHashMap<>(); + putIfNotEmpty( imageAttrs, "align", base.getAttributeValue( "align" ) ); + putIfNotEmpty( imageAttrs, "height", img.getAttributeValue( "height" ) ); + putIfNotEmpty( imageAttrs, "width", img.getAttributeValue( "width" ) ); + putIfNotEmpty( imageAttrs, "alt", img.getAttributeValue( "alt" ) ); + putIfNotEmpty( imageAttrs, "caption", emptyToNull( ( Element )XPathFactory.instance().compile( "CAPTION" ).evaluateFirst( base ) ) ); + putIfNotEmpty( imageAttrs, "link", href ); + putIfNotEmpty( imageAttrs, "border", img.getAttributeValue( "border" ) ); + putIfNotEmpty( imageAttrs, "style", base.getAttributeValue( "style" ) ); + decorateMarkupforImage( src, imageAttrs ); + } - if( n.equals( "font" ) ) - { - final String color = base.getAttributeValue( "color" ); - final String face = base.getAttributeValue( "face" ); - final String size = base.getAttributeValue( "size" ); - if( color != null ) - { - style = style + "color:" + color + ";"; - } - if( face != null ) - { - style = style + "font-family:" + face + ";"; - } - if( size != null ) - { - switch ( size ) { - case "1": - style = style + "font-size:xx-small;"; - break; - case "2": - style = style + "font-size:x-small;"; - break; - case "3": - style = style + "font-size:small;"; - break; - case "4": - style = style + "font-size:medium;"; - break; - case "5": - style = style + "font-size:large;"; - break; - case "6": - style = style + "font-size:x-large;"; - break; - case "7": - style = style + "font-size:xx-large;"; - break; - } - } + private void putIfNotEmpty( final Map< String, Object > map, final String key, final Object value ) { + if( value != null ) { + map.put( key, value ); } + } - if( style.equals( "" ) ) - { + private String emptyToNull( final Element e ) { + if( e == null ) { return null; } + final String s = e.getText(); + return s == null ? null : ( s.replaceAll( "\\s", "" ).isEmpty() ? null : s ); + } - style = style.replace( ';', '\n' ).toLowerCase(); - final LinkedHashMap< Object, Object > m = new LinkedHashMap<>(); - new PersistentMapDecorator( m ).load( new ByteArrayInputStream( style.getBytes() ) ); - return m; + void decorateMarkupforImage( final String src, final Map< String, Object > imageAttrs ) { + if( imageAttrs.isEmpty() ) { + out.print( "[" + src + "]" ); + } else { + out.print( "[{Image src='" + src + "'" ); + for( final Map.Entry< String, Object > objectObjectEntry : imageAttrs.entrySet() ) { + if ( !objectObjectEntry.getValue().equals( "" ) ) { + out.print( " " + objectObjectEntry.getKey() + "='" + objectObjectEntry.getValue() + "'" ); + } + } + out.print( "}]" ); + } + } + + private boolean isNotIgnorableWikiMarkupLink( final Element a ) { + final String ref = a.getAttributeValue( "href" ); + final String clazz = a.getAttributeValue( "class" ); + return ( ref == null || !ref.startsWith( config.getPageInfoJsp() ) ) + && ( clazz == null || !clazz.trim().equalsIgnoreCase( config.getOutlink() ) ); } - private String trimLink( String ref ) - { - if( ref == null ) - { + private String trimLink( String ref ) { + if( ref == null ) { return null; } - try - { + try { ref = URLDecoder.decode( ref, StandardCharsets.UTF_8.name() ); ref = ref.trim(); - if( ref.startsWith( m_config.getAttachPage() ) ) - { - ref = ref.substring( m_config.getAttachPage().length() ); + if( ref.startsWith( config.getAttachPage() ) ) { + ref = ref.substring( config.getAttachPage().length() ); } - if( ref.startsWith( m_config.getWikiJspPage() ) ) - { - ref = ref.substring( m_config.getWikiJspPage().length() ); + if( ref.startsWith( config.getWikiJspPage() ) ) { + ref = ref.substring( config.getWikiJspPage().length() ); // Handle links with section anchors. // For example, we need to translate the html string "TargetPage#section-TargetPage-Heading2" // to this wiki string "TargetPage#Heading2". ref = ref.replaceFirst( ".+#section-(.+)-(.+)", "$1#$2" ); } - if( ref.startsWith( m_config.getEditJspPage() ) ) - { - ref = ref.substring( m_config.getEditJspPage().length() ); + if( ref.startsWith( config.getEditJspPage() ) ) { + ref = ref.substring( config.getEditJspPage().length() ); } - if( m_config.getPageName() != null ) - { - if( ref.startsWith( m_config.getPageName() ) ) - { - ref = ref.substring( m_config.getPageName().length() ); + if( config.getPageName() != null ) { + if( ref.startsWith( config.getPageName() ) ) { + ref = ref.substring( config.getPageName().length() ); } } - } - catch ( final UnsupportedEncodingException e ) - { + } catch ( final UnsupportedEncodingException e ) { // Shouldn't happen... } return ref; } - // FIXME: These should probably be better used with java.util.Stack - - private static class LiStack - { + private class PreStack extends Stack< String > { - private StringBuffer m_li = new StringBuffer(); - - public void push(final String c ) - { - m_li.append( c ); + public String push( final String item ) { + final String push = super.push( item ); + outTrimmer.setWhitespaceTrimMode( isEmpty() ); + return push; } - public void pop() - { - m_li = m_li.deleteCharAt( m_li.length()-1 ); - // m_li = m_li.substring( 0, m_li.length() - 1 ); - } - - @Override - public String toString() - { - return m_li.toString(); + public String pop() { + final String pop = super.pop(); + outTrimmer.setWhitespaceTrimMode( isEmpty() ); + return pop; } } - private class PreStack - { - - private int m_pre; - - public boolean isPreMode() - { - return m_pre > 0; - } - - public void push() - { - m_pre++; - m_outTimmer.setWhitespaceTrimMode( !isPreMode() ); - } - - public void pop() - { - m_pre--; - m_outTimmer.setWhitespaceTrimMode( !isPreMode() ); - } - + static class ElementDecoratorData { + Element base; + String htmlBase; + String cssClass; + String cssSpecial; + boolean monospace; + boolean bold; + boolean italic; + boolean ignoredCssClass; } } diff --git a/jspwiki-util/src/main/java/org/apache/wiki/util/XmlUtil.java b/jspwiki-util/src/main/java/org/apache/wiki/util/XmlUtil.java index 3da8bf7..1b48cb6 100644 --- a/jspwiki-util/src/main/java/org/apache/wiki/util/XmlUtil.java +++ b/jspwiki-util/src/main/java/org/apache/wiki/util/XmlUtil.java @@ -134,4 +134,13 @@ public final class XmlUtil { return sb.toString(); } + public static Element getXPathElement( final Element base, final String expression ) { + final List< ? > nodes = XPathFactory.instance().compile( expression ).evaluate( base ); + if( nodes == null || nodes.size() == 0 ) { + return null; + } else { + return ( Element )nodes.get( 0 ); + } + } + } \ No newline at end of file diff --git a/jspwiki-util/src/test/java/org/apache/wiki/util/XmlUtilTest.java b/jspwiki-util/src/test/java/org/apache/wiki/util/XmlUtilTest.java index 68aa485..dc955ff 100644 --- a/jspwiki-util/src/test/java/org/apache/wiki/util/XmlUtilTest.java +++ b/jspwiki-util/src/test/java/org/apache/wiki/util/XmlUtilTest.java @@ -97,4 +97,12 @@ public class XmlUtilTest { } } + @Test + public void testGetXPathElement() { + final Element base = XmlUtil.parse( "ini/jspwiki_module.xml", "/modules" ).get( 0 ); + Assertions.assertNull( XmlUtil.getXPathElement( base, "folter" ) ); + final Element element = XmlUtil.getXPathElement( base, "filter" ); + Assertions.assertEquals( "org.apache.wiki.filters.SpamFilter", element.getAttributeValue( "class" ) ); + } + }
