001/*
002 * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.formats.html.markup;
027
028import java.io.IOException;
029import java.io.Writer;
030
031import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
032
033/**
034 * Class for generating raw HTML content to be added to HTML pages of javadoc output.
035 */
036public class RawHtml extends Content {
037
038    protected final String rawHtmlContent;
039
040    /**
041     * Creates HTML for an arbitrary string of HTML.
042     * The string is accepted as-is and is not validated in any way.
043     * It should be syntactically well-formed and contain matching {@code <} and {@code >},
044     * and matching quotes for attributes.
045     *
046     * @param rawHtml the string
047     * @return the HTML
048     */
049    public static RawHtml of(CharSequence rawHtml) {
050        return new RawHtml(rawHtml) {
051            @Override
052            public int charCount() {
053                return charCount(rawHtmlContent);
054            }
055        };
056    }
057
058    /**
059     * Creates HTML for the start of an element.
060     *
061     * @param name the name of the element
062     * @param attrs content containing any attributes
063     * @param selfClosing whether this is a self-closing element.
064     * @return the HTML
065     */
066    public static RawHtml startElement(CharSequence name, Content attrs,
067            boolean selfClosing) {
068        StringBuilder sb = new StringBuilder("<" + name);
069        if (!attrs.isEmpty()) {
070            sb.append(" ");
071            sb.append(attrs);
072        }
073        sb.append(selfClosing ? "/>" : ">");
074        return new RawHtml(sb);
075    }
076
077    /**
078     * Creates HTML for the end of an element.
079     *
080     * @param name the name of the element
081     * @return the HTML
082     */
083    public static RawHtml endElement(CharSequence name) {
084        return new RawHtml("</" + name + ">");
085    }
086
087    /**
088     * Creates HTML for an HTML comment.
089     *
090     * The body will be enclosed in {@code <!--} and {@code -->} if it does not
091     * already begin and end with those sequences.
092     *
093     * @param body the body of the comment
094     *
095     * @return the HTML
096     */
097    public static RawHtml comment(String body) {
098        return section("<!--", body, "-->");
099    }
100
101    /**
102     * Creates HTML for an HTML CDATA section.
103     *
104     * The body will be enclosed in {@code <![CDATA]} and {@code ]]>} if it does not
105     * already begin and end with those sequences.
106     *
107     * @param body the body of the CDATA section
108     *
109     * @return the HTML
110     */
111    public static RawHtml cdata(String body) {
112        return section("<![CDATA[", body, "]]>");
113    }
114
115    private static RawHtml section(String prefix, String body, String suffix) {
116        return new RawHtml(
117            body.startsWith(prefix) && body.endsWith(suffix) ? body
118                : prefix + body + suffix);
119    }
120
121    /**
122     * Constructor to construct a RawHtml object.
123     *
124     * @param rawHtml raw HTML text to be added
125     */
126    private RawHtml(CharSequence rawHtml) {
127        assert Text.checkNewlines(rawHtml);
128        rawHtmlContent = rawHtml.toString();
129    }
130
131    @Override
132    public boolean isEmpty() {
133        return rawHtmlContent.isEmpty();
134    }
135
136    @Override
137    public String toString() {
138        return rawHtmlContent;
139    }
140
141    private enum State {
142        TEXT, ENTITY, TAG, STRING
143    }
144
145    protected static int charCount(CharSequence htmlText) {
146        State state = State.TEXT;
147        int count = 0;
148        for (int i = 0; i < htmlText.length(); i++) {
149            char c = htmlText.charAt(i);
150            switch (state) {
151            case TEXT:
152                switch (c) {
153                case '<':
154                    state = State.TAG;
155                    break;
156                case '&':
157                    state = State.ENTITY;
158                    count++;
159                    break;
160                default:
161                    count++;
162                }
163                break;
164
165            case ENTITY:
166                if (!Character.isLetterOrDigit(c))
167                    state = State.TEXT;
168                break;
169
170            case TAG:
171                switch (c) {
172                case '"':
173                    state = State.STRING;
174                    break;
175                case '>':
176                    state = State.TEXT;
177                    break;
178                }
179                break;
180
181            case STRING:
182                switch (c) {
183                case '"':
184                    state = State.TAG;
185                    break;
186                }
187            }
188        }
189        return count;
190    }
191
192    @Override
193    public boolean write(Writer out, String newline, boolean atNewline)
194            throws IOException {
195        out.write(rawHtmlContent.replace("\n", newline));
196        return rawHtmlContent.endsWith("\n");
197    }
198}