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