001package org.apache.maven.doxia.sink.impl;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Enumeration;
023import java.util.Arrays;
024
025import javax.swing.text.AttributeSet;
026import javax.swing.text.MutableAttributeSet;
027
028import org.apache.maven.doxia.markup.Markup;
029import org.apache.maven.doxia.sink.SinkEventAttributes;
030
031/**
032 * Collection of common utility methods for sinks.
033 *
034 * @author ltheussl
035 * @version $Id$
036 * @since 1.1
037 */
038public class SinkUtils
039{
040
041    /** Do not instantiate. */
042    private SinkUtils()
043    {
044        // Utility class
045    }
046
047    /**
048     * The set of base attributes.
049     */
050    public static final String[] SINK_BASE_ATTRIBUTES =
051    {
052        SinkEventAttributes.CLASS, SinkEventAttributes.ID, SinkEventAttributes.LANG,
053        SinkEventAttributes.STYLE, SinkEventAttributes.TITLE
054    };
055
056    /**
057     * The attributes that are supported for the br tag.
058     */
059    public static final String[] SINK_BR_ATTRIBUTES =
060    {
061        SinkEventAttributes.CLASS, SinkEventAttributes.ID,
062        SinkEventAttributes.STYLE, SinkEventAttributes.TITLE
063    };
064
065    /**
066     * The attributes that are supported for the <img> tag.
067     */
068    public static final String[] SINK_IMG_ATTRIBUTES;
069
070    /**
071     * The attributes that are supported for the section tags, like <p>, <h2>, <div>.
072     */
073    public static final String[] SINK_SECTION_ATTRIBUTES;
074
075    /**
076     * The attributes that are supported for the <div> and <pre> tags.
077     */
078    public static final String[] SINK_VERBATIM_ATTRIBUTES;
079
080    /**
081     * The attributes that are supported for the <hr> tag.
082     */
083    public static final String[] SINK_HR_ATTRIBUTES;
084
085    /**
086     * The attributes that are supported for the <a> tag.
087     */
088    public static final String[] SINK_LINK_ATTRIBUTES;
089
090    /**
091     * The attributes that are supported for the <table> tag.
092     */
093    public static final String[] SINK_TABLE_ATTRIBUTES;
094
095    /**
096     * The attributes that are supported for the <td> and <th> tags.
097     */
098    public static final String[] SINK_TD_ATTRIBUTES;
099
100    /**
101     * The attributes that are supported for the <tr> tag.
102     */
103    public static final String[] SINK_TR_ATTRIBUTES;
104
105    private static final String[] IMG_ATTRIBUTES =
106    {
107        SinkEventAttributes.ALIGN, SinkEventAttributes.ALT, SinkEventAttributes.BORDER,
108        SinkEventAttributes.HEIGHT, SinkEventAttributes.HSPACE, SinkEventAttributes.ISMAP,
109        SinkEventAttributes.SRC, SinkEventAttributes.USEMAP, SinkEventAttributes.VSPACE,
110        SinkEventAttributes.WIDTH
111    };
112
113    private static final String[] HR_ATTRIBUTES =
114    {
115        SinkEventAttributes.ALIGN, SinkEventAttributes.NOSHADE, SinkEventAttributes.SIZE,
116        SinkEventAttributes.WIDTH
117    };
118
119    private static final String[] LINK_ATTRIBUTES =
120    {
121        SinkEventAttributes.CHARSET, SinkEventAttributes.COORDS, SinkEventAttributes.HREF,
122        SinkEventAttributes.HREFLANG, SinkEventAttributes.REL, SinkEventAttributes.REV,
123        SinkEventAttributes.SHAPE, SinkEventAttributes.TARGET, SinkEventAttributes.TYPE
124    };
125
126    private static final String[] TABLE_ATTRIBUTES =
127    {
128        SinkEventAttributes.ALIGN, SinkEventAttributes.BGCOLOR, SinkEventAttributes.BORDER,
129        SinkEventAttributes.CELLPADDING, SinkEventAttributes.CELLSPACING, SinkEventAttributes.FRAME,
130        SinkEventAttributes.RULES, SinkEventAttributes.SUMMARY, SinkEventAttributes.WIDTH
131    };
132
133    private static final String[] TABLE_CELL_ATTRIBUTES =
134    {
135        SinkEventAttributes.ABBRV, SinkEventAttributes.ALIGN, SinkEventAttributes.AXIS,
136        SinkEventAttributes.BGCOLOR, SinkEventAttributes.COLSPAN, SinkEventAttributes.HEADERS,
137        SinkEventAttributes.HEIGHT, SinkEventAttributes.NOWRAP, SinkEventAttributes.ROWSPAN,
138        SinkEventAttributes.SCOPE, SinkEventAttributes.VALIGN, SinkEventAttributes.WIDTH
139    };
140
141    static
142    {
143        SINK_IMG_ATTRIBUTES = join( SINK_BASE_ATTRIBUTES, IMG_ATTRIBUTES );
144        SINK_SECTION_ATTRIBUTES =
145                join( SINK_BASE_ATTRIBUTES, new String[] {SinkEventAttributes.ALIGN} );
146        SINK_VERBATIM_ATTRIBUTES =
147                join( SINK_BASE_ATTRIBUTES,
148                new String[] {SinkEventAttributes.ALIGN, SinkEventAttributes.DECORATION, SinkEventAttributes.WIDTH} );
149        SINK_HR_ATTRIBUTES = join( SINK_BASE_ATTRIBUTES, HR_ATTRIBUTES );
150        SINK_LINK_ATTRIBUTES = join( SINK_BASE_ATTRIBUTES, LINK_ATTRIBUTES );
151        SINK_TABLE_ATTRIBUTES = join( SINK_BASE_ATTRIBUTES, TABLE_ATTRIBUTES );
152        SINK_TR_ATTRIBUTES =
153                join( SINK_BASE_ATTRIBUTES,
154                new String[] {SinkEventAttributes.ALIGN, SinkEventAttributes.BGCOLOR, SinkEventAttributes.VALIGN} );
155        SINK_TD_ATTRIBUTES = join( SINK_BASE_ATTRIBUTES, TABLE_CELL_ATTRIBUTES );
156    }
157
158    private static String[] join( String[] a, String[] b )
159    {
160        String[] temp = new String[a.length + b.length];
161        System.arraycopy( a, 0, temp, 0, a.length );
162        System.arraycopy( b, 0, temp, a.length, b.length );
163
164        Arrays.sort( temp ); // necessary for binary searches in filterAttributes()
165
166        return temp;
167    }
168
169    /**
170     * Utility method to get an AttributeSet as a String.
171     * The resulting String is in the form ' name1="value1" name2="value2" ...',
172     * ie it can be appended directly to an xml start tag. Attribute values that are itself
173     * AttributeSets are ignored unless the Attribute name is SinkEventAttributeSet.STYLE,
174     * in which case they are written as outlined at
175     * {@link org.apache.maven.doxia.sink.SinkEventAttributes#STYLE SinkEventAttributes.STYLE}.
176     * All other keys and values are written as Strings.
177     *
178     * @param att The AttributeSet. May be null, in which case an empty String is returned.
179     * @return the AttributeSet as a String in a form that can be appended to an xml start tag.
180     */
181    public static String getAttributeString( AttributeSet att )
182    {
183        if ( att == null )
184        {
185            return "";
186        }
187
188        StringBuilder sb = new StringBuilder();
189
190        Enumeration<?> names = att.getAttributeNames();
191
192        while ( names.hasMoreElements() )
193        {
194            Object key = names.nextElement();
195            Object value = att.getAttribute( key );
196
197            if ( value instanceof AttributeSet )
198            {
199                // Other AttributeSets are ignored
200                if ( SinkEventAttributes.STYLE.equals( key.toString() ) )
201                {
202                    sb.append( Markup.SPACE ).append( key.toString() ).append( Markup.EQUAL )
203                        .append( Markup.QUOTE ).append( asCssString( (AttributeSet) value ) )
204                        .append( Markup.QUOTE );
205                }
206            }
207            else
208            {
209                sb.append( Markup.SPACE ).append( key.toString() ).append( Markup.EQUAL )
210                    .append( Markup.QUOTE ).append( value.toString() ).append( Markup.QUOTE );
211            }
212        }
213
214        return sb.toString();
215    }
216
217    private static String asCssString( AttributeSet att )
218    {
219        StringBuilder sb = new StringBuilder();
220
221        Enumeration<?> names = att.getAttributeNames();
222
223        while ( names.hasMoreElements() )
224        {
225            Object key = names.nextElement();
226            Object value = att.getAttribute( key );
227
228            // don't go recursive
229            if ( !( value instanceof AttributeSet ) )
230            {
231                sb.append( key.toString() ).append( Markup.COLON )
232                    .append( Markup.SPACE ).append( value.toString() );
233
234                if ( names.hasMoreElements() )
235                {
236                    sb.append( Markup.SEMICOLON ).append( Markup.SPACE );
237                }
238            }
239        }
240
241        return sb.toString();
242    }
243
244    /**
245     * Filters the given AttributeSet.
246     * Removes all attributes whose name (key) is not contained in the sorted array valids.
247     *
248     * @param attributes The AttributeSet to filter. The String values of Attribute names
249     * are compared to the elements of the valids array.
250     * @param valids a sorted array of attribute names that are to be kept in the resulting AttributeSet.
251     *      <b>Note:</b> a binary search is employed, so the array has to be sorted for correct results.
252     * @return A filtered MutableAttributeSet object. Returns null if the input AttributeSet is null.
253     *      If the array of valids is either null or empty, an empty AttributeSet is returned.
254     */
255    public static MutableAttributeSet filterAttributes( AttributeSet attributes, String[] valids )
256    {
257        if ( attributes == null )
258        {
259            return null;
260        }
261
262        if ( valids == null || valids.length == 0 )
263        {
264            return new SinkEventAttributeSet( 0 );
265        }
266
267        MutableAttributeSet atts = new SinkEventAttributeSet( attributes.getAttributeCount() );
268
269        Enumeration<?> names = attributes.getAttributeNames();
270
271        while ( names.hasMoreElements() )
272        {
273            String key = names.nextElement().toString();
274
275            if ( Arrays.binarySearch( valids, key ) >= 0 )
276            {
277                atts.addAttribute( key, attributes.getAttribute( key ) );
278            }
279        }
280
281        return atts;
282    }
283}