001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.html;
018
019import static org.conqat.lib.commons.html.EHTMLAttribute.SRC;
020import static org.conqat.lib.commons.html.EHTMLAttribute.TYPE;
021import static org.conqat.lib.commons.html.EHTMLElement.SCRIPT;
022
023import java.io.File;
024import java.io.IOException;
025import java.io.OutputStream;
026import java.io.OutputStreamWriter;
027import java.io.PrintStream;
028import java.io.PrintWriter;
029import java.io.UnsupportedEncodingException;
030
031import org.conqat.lib.commons.filesystem.FileSystemUtils;
032import org.conqat.lib.commons.string.StringUtils;
033import org.conqat.lib.commons.xml.IXMLResolver;
034import org.conqat.lib.commons.xml.XMLWriter;
035
036/**
037 * This class is used for writing HTML.
038 */
039public class HTMLWriter extends XMLWriter<EHTMLElement, EHTMLAttribute> {
040
041        /** The CSS manager class used. */
042        private final CSSManagerBase cssManager;
043
044        /**
045         * Creates a new writer for HTML documents.
046         * 
047         * @param file
048         *            the file to write to.
049         * @param cssManager
050         *            the CSS manager used. If this is null, the class attributes may be
051         *            filled with simple strings.
052         */
053        public HTMLWriter(File file, CSSManagerBase cssManager) throws IOException {
054                this(new PrintStream(file, FileSystemUtils.UTF8_ENCODING), cssManager);
055        }
056
057        /**
058         * Creates a new writer for HTML documents.
059         * 
060         * @param stream
061         *            the stream to print to.
062         * @param cssManager
063         *            the CSS manager used. If this is null, the class attributes may be
064         *            filled with simple strings.
065         */
066        public HTMLWriter(OutputStream stream, CSSManagerBase cssManager) {
067                super(new PrintWriter(wrapStream(stream)), new HTMLResolver());
068                this.cssManager = cssManager;
069        }
070
071        // Fix comment finding?
072        /**
073         * Helper method for {@link #HTMLWriter(OutputStream, CSSManagerBase)} to deal
074         * with the exception.
075         */
076        private static OutputStreamWriter wrapStream(OutputStream stream) {
077                try {
078                        return new OutputStreamWriter(stream, FileSystemUtils.UTF8_ENCODING);
079                } catch (UnsupportedEncodingException e) {
080                        throw new AssertionError("UTF-8 should be supported!");
081                }
082        }
083
084        /**
085         * Creates a new writer for HTML documents.
086         * 
087         * @param writer
088         *            the writer to print to.
089         */
090        public HTMLWriter(PrintWriter writer, CSSManagerBase cssManager) {
091                super(writer, new HTMLResolver());
092                this.cssManager = cssManager;
093        }
094
095        /**
096         * This adds a default header for HTML files consisting of the XML header and a
097         * DOCTYPE of the xhtml frameset DTD.
098         * <p>
099         * XML version is set to "1.0", encoding provided by a parameter, and doc type
100         * definition to XHTML 1.0 Frameset.
101         */
102        public void addStdHeader(String encoding) {
103                addHeader("1.0", encoding);
104                addPublicDocTypeDefintion(EHTMLElement.HTML, "-//W3C//DTD XHTML 1.0 Frameset//EN",
105                                "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd");
106        }
107
108        /**
109         * This adds a default header for HTML files consisting of the XML header and a
110         * DOCTYPE of the xhtml frameset DTD.
111         * <p>
112         * XML version is set to "1.0", encoding to "UTF-8", and doc type definition to
113         * XHTML 1.0 Frameset.
114         */
115        public void addStdHeader() {
116                addStdHeader(FileSystemUtils.UTF8_ENCODING);
117        }
118
119        /**
120         * {@inheritDoc}
121         * 
122         * Made this public here.
123         */
124        @Override
125        public void addRawString(String html) {
126                super.addRawString(html);
127        }
128
129        /**
130         * Adds a line separator with closing and open tag (see {@link #addNewLine()}.
131         */
132        public void addRawNewLine() {
133                addRawString(StringUtils.LINE_SEPARATOR);
134        }
135
136        /** Inserts a script tag that loads JavaScript from a separate file. */
137        public void addExternalJavaScript(String scriptFilePath) {
138                // this is required, as some browsers choke on a directly closed
139                // script element
140                insertEmptyElement(SCRIPT, SRC, scriptFilePath, TYPE, "text/javascript");
141        }
142
143        /**
144         * Inserts an empty element but ensures that it is not closed using the
145         * shorthand syntax (e.g. <code>&lt;div /&gt;</code>).
146         * 
147         * This is useful since some browsers choke on some shorthand elements, e.g.
148         * divs or JavaScript tags.
149         * 
150         * @see #openElement(EHTMLElement, Object...)
151         */
152        public void insertEmptyElement(EHTMLElement element, Object... attributes) {
153                openElement(element, attributes);
154                addText(StringUtils.EMPTY_STRING);
155                closeElement(element);
156        }
157
158        /** Inserts a non-breaking space. */
159        public void addNonBreakingSpace() {
160                addRawString("&nbsp;");
161        }
162
163        /**
164         * Adds an attribute to the currently open element but checks in addition if the
165         * attribute may be added at all.
166         * 
167         * @throws HTMLWriterException
168         *             if the attribute is not allowed for the current element.
169         */
170        @Override
171        public void addAttribute(EHTMLAttribute attribute, Object value) {
172                if (!getCurrentElement().allowsAttribute(attribute)) {
173                        throw new HTMLWriterException("Attribute " + attribute + " not allowed for element " + getCurrentElement());
174                }
175
176                if (attribute == EHTMLAttribute.STYLE) {
177                        assertCssDeclarationBlock(value);
178                        value = ((CSSDeclarationBlock) value).toInlineStyle();
179                } else if (cssManager != null && attribute == EHTMLAttribute.CLASS) {
180                        assertCssDeclarationBlock(value);
181                        value = cssManager.getCSSClassName((CSSDeclarationBlock) value);
182                }
183
184                super.addAttribute(attribute, value);
185        }
186
187        /**
188         * Asserts that the given value is a {@link CSSDeclarationBlock} and throws an
189         * exception otherwise.
190         */
191        private static void assertCssDeclarationBlock(Object value) {
192                if (!(value instanceof CSSDeclarationBlock)) {
193                        throw new HTMLWriterException("The argument for STYLE and CLASS attributes must be a "
194                                        + CSSDeclarationBlock.class.getSimpleName() + "!");
195                }
196        }
197
198        /** The resolver used for the {@link HTMLWriter}. */
199        public static class HTMLResolver implements IXMLResolver<EHTMLElement, EHTMLAttribute> {
200
201                /** {@inheritDoc} */
202                @Override
203                public String resolveAttributeName(EHTMLAttribute attribute) {
204                        return attribute.toString();
205                }
206
207                /** {@inheritDoc} */
208                @Override
209                public String resolveElementName(EHTMLElement element) {
210                        return element.toString();
211                }
212
213                /** {@inheritDoc} */
214                @Override
215                public Class<EHTMLAttribute> getAttributeClass() {
216                        return EHTMLAttribute.class;
217                }
218        }
219}