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.module.xdoc;
020
021import javax.swing.text.MutableAttributeSet;
022import javax.swing.text.html.HTML.Attribute;
023
024import java.io.Writer;
025
026import org.apache.maven.doxia.sink.SinkEventAttributes;
027import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
028import org.apache.maven.doxia.sink.impl.SinkUtils;
029import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink;
030import org.apache.maven.doxia.util.DoxiaStringUtils;
031import org.apache.maven.doxia.util.HtmlTools;
032
033/**
034 * <a href="https://maven.apache.org/doxia/references/xdoc-format.html">Xdoc</a> Sink implementation.
035 * <br>
036 * It uses the Xdoc XSD <a href="https://maven.apache.org/xsd/xdoc-2.0.xsd">
037 * https://maven.apache.org/xsd/xdoc-2.0.xsd</a>.
038 *
039 * @author <a href="mailto:james@jamestaylor.org">James Taylor</a>
040 * @since 1.0
041 */
042public class XdocSink extends Xhtml5BaseSink implements XdocMarkup {
043    // ----------------------------------------------------------------------
044    // Instance fields
045    // ----------------------------------------------------------------------
046
047    private String encoding;
048
049    private String languageId;
050
051    // ----------------------------------------------------------------------
052    // Constructors
053    // ----------------------------------------------------------------------
054
055    /**
056     * Constructor, initialize the Writer.
057     *
058     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
059     */
060    protected XdocSink(Writer writer) {
061        super(writer);
062    }
063
064    /**
065     * Constructor, initialize the Writer and tells which encoding is used.
066     *
067     * @param writer not null writer to write the result.
068     * @param encoding the encoding used, that should be written to the generated HTML content
069     * if not <code>null</code>.
070     * @since 1.1
071     */
072    protected XdocSink(Writer writer, String encoding) {
073        this(writer);
074        this.encoding = encoding;
075    }
076
077    /**
078     * Constructor, initialize the Writer and tells which encoding and languageId are used.
079     *
080     * @param writer not null writer to write the result.
081     * @param encoding the encoding used, that should be written to the generated HTML content
082     * if not <code>null</code>.
083     * @param languageId language identifier for the root element as defined by
084     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
085     * in addition, the empty string may be specified.
086     * @since 1.1
087     */
088    protected XdocSink(Writer writer, String encoding, String languageId) {
089        this(writer, encoding);
090
091        this.languageId = languageId;
092    }
093
094    // ----------------------------------------------------------------------
095    // Public protected methods
096    // ----------------------------------------------------------------------
097
098    /**
099     * {@inheritDoc}
100     */
101    protected void init() {
102        super.init();
103    }
104
105    /**
106     * {@inheritDoc}
107     * @see XdocMarkup#DOCUMENT_TAG
108     * @see XdocMarkup#PROPERTIES_TAG
109     */
110    public void head(SinkEventAttributes attributes) {
111        init();
112
113        setHeadFlag(true);
114
115        write("<?xml version=\"1.0\"");
116        if (encoding != null) {
117            write(" encoding=\"" + encoding + "\"");
118        }
119        write("?>");
120
121        MutableAttributeSet atts = new SinkEventAttributeSet();
122        atts.addAttribute("xmlns", XDOC_NAMESPACE);
123        atts.addAttribute("xmlns:xsi", XML_NAMESPACE);
124        atts.addAttribute("xsi:schemaLocation", XDOC_NAMESPACE + " " + XDOC_SYSTEM_ID);
125
126        if (languageId != null) {
127            atts.addAttribute(Attribute.LANG.toString(), languageId);
128            atts.addAttribute("xml:lang", languageId);
129        }
130
131        if (attributes != null) {
132            atts.addAttributes(attributes);
133        }
134
135        writeStartTag(DOCUMENT_TAG, atts);
136
137        writeStartTag(PROPERTIES_TAG);
138    }
139
140    /**
141     * {@inheritDoc}
142     *
143     * @see XdocMarkup#DOCUMENT_TAG
144     * @see XdocMarkup#PROPERTIES_TAG
145     */
146    public void head_() {
147        setHeadFlag(false);
148
149        writeEndTag(PROPERTIES_TAG);
150    }
151
152    /**
153     * {@inheritDoc}
154     *
155     * @see javax.swing.text.html.HTML.Tag#TITLE
156     */
157    @Override
158    public void title(SinkEventAttributes attributes) {
159        writeStartTag(TITLE);
160    }
161
162    /**
163     * {@inheritDoc}
164     *
165     * @see javax.swing.text.html.HTML.Tag#TITLE
166     */
167    public void title_() {
168        content(getTextBuffer().toString());
169
170        writeEndTag(TITLE);
171
172        resetTextBuffer();
173    }
174
175    /**
176     * {@inheritDoc}
177     *
178     * @see XdocMarkup#AUTHOR_TAG
179     */
180    public void author_() {
181        if (getTextBuffer().length() > 0) {
182            writeStartTag(AUTHOR_TAG);
183            String text = HtmlTools.escapeHTML(getTextBuffer().toString());
184            // hack: un-escape numerical entities that have been escaped above
185            // note that numerical entities should really be written as one unicode character in the first place
186            text = DoxiaStringUtils.replace(text, "&amp;#", "&#");
187            write(text);
188            writeEndTag(AUTHOR_TAG);
189            resetTextBuffer();
190        }
191    }
192
193    /**
194     * {@inheritDoc}
195     *
196     * @see XdocMarkup#DATE_TAG
197     */
198    public void date_() {
199        if (getTextBuffer().length() > 0) {
200            writeStartTag(DATE_TAG);
201            content(getTextBuffer().toString());
202            writeEndTag(DATE_TAG);
203            resetTextBuffer();
204        }
205    }
206
207    /**
208     * {@inheritDoc}
209     * @see javax.swing.text.html.HTML.Tag#BODY
210     */
211    public void body(SinkEventAttributes attributes) {
212        writeStartTag(BODY, attributes);
213    }
214
215    /**
216     * {@inheritDoc}
217     *
218     * @see javax.swing.text.html.HTML.Tag#BODY
219     * @see XdocMarkup#DOCUMENT_TAG
220     */
221    public void body_() {
222        writeEndTag(BODY);
223
224        writeEndTag(DOCUMENT_TAG);
225
226        flush();
227
228        init();
229    }
230
231    // ----------------------------------------------------------------------
232    // Sections
233    // ----------------------------------------------------------------------
234
235    /**
236     * {@inheritDoc}
237     *
238     * Starts a section.
239     * @see XdocMarkup#SECTION_TAG
240     * @see XdocMarkup#SUBSECTION_TAG
241     */
242    protected void onSection(int depth, SinkEventAttributes attributes) {
243        if (depth == SECTION_LEVEL_1) {
244            write(LESS_THAN
245                    + SECTION_TAG.toString()
246                    + SinkUtils.getAttributeString(
247                            SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES))
248                    + SPACE
249                    + Attribute.NAME
250                    + EQUAL
251                    + QUOTE);
252        } else if (depth == SECTION_LEVEL_2) {
253            write(LESS_THAN
254                    + SUBSECTION_TAG.toString()
255                    + SinkUtils.getAttributeString(
256                            SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES))
257                    + SPACE
258                    + Attribute.NAME
259                    + EQUAL
260                    + QUOTE);
261        }
262    }
263
264    /**
265     * {@inheritDoc}
266     *
267     * Ends a section.
268     * @see XdocMarkup#SECTION_TAG
269     * @see XdocMarkup#SUBSECTION_TAG
270     */
271    protected void onSection_(int depth) {
272        if (depth == SECTION_LEVEL_1) {
273            writeEndTag(SECTION_TAG);
274        } else if (depth == SECTION_LEVEL_2) {
275            writeEndTag(SUBSECTION_TAG);
276        }
277    }
278
279    /**
280     * {@inheritDoc}
281     *
282     * Starts a section title.
283     * @see #H3
284     * @see #H4
285     * @see #H5
286     * @see #H6
287     */
288    protected void onSectionTitle(int depth, SinkEventAttributes attributes) {
289        MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
290
291        if (depth == SECTION_LEVEL_3) {
292            writeStartTag(H3, atts);
293        } else if (depth == SECTION_LEVEL_4) {
294            writeStartTag(H4, atts);
295        } else if (depth == SECTION_LEVEL_5) {
296            writeStartTag(H5, atts);
297        } else if (depth == SECTION_LEVEL_6) {
298            writeStartTag(H6, atts);
299        }
300    }
301
302    /**
303     * {@inheritDoc}
304     *
305     * Ends a section title.
306     * @see #H3
307     * @see #H4
308     * @see #H5
309     * @see #H6
310     */
311    protected void onSectionTitle_(int depth) {
312        if (depth == SECTION_LEVEL_1 || depth == SECTION_LEVEL_2) {
313            write(String.valueOf(QUOTE) + GREATER_THAN);
314        } else if (depth == SECTION_LEVEL_3) {
315            writeEndTag(H3);
316        } else if (depth == SECTION_LEVEL_4) {
317            writeEndTag(H4);
318        } else if (depth == SECTION_LEVEL_5) {
319            writeEndTag(H5);
320        } else if (depth == SECTION_LEVEL_6) {
321            writeEndTag(H6);
322        }
323    }
324
325    // -----------------------------------------------------------------------
326    //
327    // -----------------------------------------------------------------------
328
329    /**
330     * {@inheritDoc}
331     *
332     * @see XdocMarkup#SOURCE_TAG
333     * @see javax.swing.text.html.HTML.Tag#PRE
334     * @param attributes a {@link org.apache.maven.doxia.sink.SinkEventAttributes} object.
335     */
336    public void verbatim(SinkEventAttributes attributes) {
337
338        MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES);
339
340        if (atts == null) {
341            atts = new SinkEventAttributeSet();
342        }
343
344        this.setVerbatimMode(VerbatimMode.ON);
345        if (atts.isDefined(SinkEventAttributes.DECORATION)) {
346            if ("source".equals(atts.getAttribute(SinkEventAttributes.DECORATION))) {
347                this.setVerbatimMode(VerbatimMode.ON_WITH_CODE);
348            }
349        }
350
351        atts.removeAttribute(SinkEventAttributes.DECORATION);
352
353        if (getVerbatimMode() == VerbatimMode.ON_WITH_CODE) {
354            writeStartTag(SOURCE_TAG, atts);
355        } else {
356            writeStartTag(PRE, atts);
357        }
358    }
359
360    /**
361     * {@inheritDoc}
362     *
363     * @see XdocMarkup#SOURCE_TAG
364     * @see javax.swing.text.html.HTML.Tag#PRE
365     */
366    public void verbatim_() {
367        if (getVerbatimMode() == VerbatimMode.ON_WITH_CODE) {
368            writeEndTag(SOURCE_TAG);
369        } else {
370            writeEndTag(PRE);
371        }
372
373        this.setVerbatimMode(VerbatimMode.OFF);
374    }
375
376    /**
377     * {@inheritDoc}
378     *
379     * @see javax.swing.text.html.HTML.Tag#TABLE
380     */
381    public void tableRows(int[] justification, boolean grid) {
382        // similar to super.tableRows(justification, grid) but without class.
383
384        setCellJustif(justification);
385
386        MutableAttributeSet att = new SinkEventAttributeSet();
387
388        if (!tableAttributes.isDefined(Attribute.BORDER.toString())) {
389            att.addAttribute(Attribute.BORDER, (grid ? "1" : "0"));
390        }
391
392        att.addAttributes(tableAttributes);
393
394        tableAttributes.removeAttributes(tableAttributes);
395
396        writeStartTag(TABLE, att);
397    }
398
399    /**
400     * {@inheritDoc}
401     *
402     * @see javax.swing.text.html.HTML.Tag#TR
403     */
404    @Override
405    public void tableRow(SinkEventAttributes attributes) {
406
407        writeStartTag(TR, attributes);
408
409        setCellCount(0);
410    }
411
412    /**
413     * <p>close.</p>
414     */
415    public void close() {
416        super.close();
417
418        init();
419    }
420
421    // ----------------------------------------------------------------------
422    //
423    // ----------------------------------------------------------------------
424
425}