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