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