001/*
002 * Copyright (c) 2019, 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 * A representation of HTML entities.
035 */
036public class Entity extends Content {
037    public static final Entity LESS_THAN = new Entity("<");
038    public static final Entity GREATER_THAN = new Entity(">");
039    public static final Entity AMPERSAND = new Entity("&");
040    public static final Entity NO_BREAK_SPACE = new Entity(" ");
041
042    public final String text;
043
044    /**
045     * Creates an entity with a given name or numeric value.
046     *
047     * @param name the name, or numeric value
048     * @return the entity
049     */
050    public static Entity of(CharSequence name) {
051        return new Entity("&" + name + ";");
052    }
053
054    private Entity(String text) {
055        this.text = text;
056    }
057
058    @Override
059    public boolean write(Writer writer, String newline, boolean atNewline)
060            throws IOException {
061        writer.write(text);
062        return false;
063    }
064
065    @Override
066    public boolean isEmpty() {
067        return false;
068    }
069
070    @Override
071    public int charCount() {
072        return 1;
073    }
074
075    /**
076     * Escapes the special HTML characters in a given string using the appropriate
077     * entities.
078     *
079     * @param s the string to escape
080     * @return the string with all of the HTML characters escaped
081     */
082    static String escapeHtmlChars(CharSequence s) {
083        // Convert to string as CharSequence implementations can be slow - see
084        // JDK-8263321
085        String str = s.toString();
086        for (int i = 0; i < str.length(); i++) {
087            char ch = str.charAt(i);
088            switch (ch) {
089            // only start building a new string if we need to
090            case '<', '>', '&' -> {
091                StringBuilder sb = new StringBuilder(str.substring(0, i));
092                escapeHtmlChars(str, i, sb);
093                return sb.toString();
094            }
095            }
096        }
097        return str;
098    }
099
100    /**
101     * Escapes the special HTML characters in a given string using the appropriate
102     * entities, appending the results into a string builder.
103     *
104     * @param s the string
105     * @param sb the string builder
106     */
107    static void escapeHtmlChars(CharSequence s, StringBuilder sb) {
108        escapeHtmlChars(s.toString(), 0, sb);
109    }
110
111    private static void escapeHtmlChars(String s, int start, StringBuilder sb) {
112        for (int i = start; i < s.length(); i++) {
113            char ch = s.charAt(i);
114            switch (ch) {
115            case '<' -> sb.append(Entity.LESS_THAN.text);
116            case '>' -> sb.append(Entity.GREATER_THAN.text);
117            case '&' -> sb.append(Entity.AMPERSAND.text);
118            default -> sb.append(ch);
119            }
120        }
121    }
122
123}