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