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