001// Copyright 2006, 2007, 2008, 2009, 2010, 2014 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.dom;
016
017import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
018import org.apache.tapestry5.ioc.internal.util.InternalUtils;
019
020import java.io.PrintWriter;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024
025/**
026 * The root node of a DOM.
027 */
028public final class Document extends Node
029{
030    /**
031     * XML Namespace URI. May be bound to the "xml" but must not be bound to any other prefix.
032     */
033    public static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
034
035    /**
036     * Namespace used exclusively for defining namespaces.
037     */
038    public static final String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
039
040    private Element rootElement;
041
042    private DTD dtd;
043
044    private final MarkupModel model;
045
046    private final String encoding;
047    
048    private final String mimeType;
049
050    /**
051     * Non-element content that comes between the DOCTYPE and the root element.
052     */
053    private List<Node> preamble;
054
055    /**
056     * Same as Document(model, null).
057     * @param model a {@link MarkupModel}.
058     */
059    public Document(final MarkupModel model)
060    {
061        this(model, null);
062    }
063
064    /**
065     * Same as Document(model, encoding, null).
066     * @param model a {@link MarkupModel}.
067     */
068    public Document(final MarkupModel model, final String encoding) {
069        this(model, encoding, null);
070    }
071
072    /**
073     * Creates a document instance with a given markup model, encoding and MIME type.
074     * @param model a {@link MarkupModel}.
075     * @param encoding the encoding.
076     * @param mimeType the MIME type.
077     * @since 5.4
078     */
079    public Document(final MarkupModel model, final String encoding, final String mimeType)
080    {
081        super(null);
082
083        assert model != null;
084
085        this.model = model;
086        this.encoding = encoding;
087        this.mimeType = mimeType;
088    }
089
090    @Override
091    public Document getDocument()
092    {
093        return this;
094    }
095
096    /**
097     * Finds an element based on a path of element names.
098     *
099     * @param path slash separated series of element names
100     * @return the matching element, or null if not found
101     * @see Element#find(String)
102     */
103    public Element find(String path)
104    {
105        assert InternalUtils.isNonBlank(path);
106
107        if (rootElement == null)
108            return null;
109
110        int slashx = path.indexOf("/");
111
112        String rootElementName = slashx < 0 ? path : path.substring(0, slashx);
113
114        if (!rootElement.getName().equals(rootElementName))
115            return null;
116
117        return slashx < 0 ? rootElement : rootElement.find(path.substring(slashx + 1));
118    }
119
120    /**
121     * Builds with an instance of {@link DefaultMarkupModel}.
122     */
123    public Document()
124    {
125        this(new DefaultMarkupModel());
126    }
127
128    public MarkupModel getMarkupModel()
129    {
130        return model;
131    }
132
133    /**
134     * Creates the root element for this document, replacing any previous root element.
135     */
136    public Element newRootElement(String name)
137    {
138        rootElement = new Element(this, null, name);
139
140        return rootElement;
141    }
142
143    /**
144     * Creates a new root element within a namespace.
145     *
146     * @param namespace URI of namespace containing the element
147     * @param name      name of element with namespace
148     * @return the root element
149     */
150    public Element newRootElement(String namespace, String name)
151    {
152        rootElement = new Element(this, namespace, name);
153
154        return rootElement;
155    }
156
157    @Override
158    public void toMarkup(Document document, PrintWriter writer, Map<String, String> namespaceURIToPrefix)
159    {
160        if (model.isXML())
161        {
162            writer.print("<?xml version=\"1.0\"");
163
164            if (encoding != null)
165                writer.printf(" encoding=\"%s\"", encoding);
166
167            writer.print("?>\n");
168        }
169        if (dtd != null)
170        {
171            dtd.toMarkup(writer);
172        }
173
174        if (preamble != null)
175        {
176            for (Node n : preamble)
177                n.toMarkup(this, writer, namespaceURIToPrefix);
178        }
179
180        if (rootElement == null)
181            return;
182
183        Map<String, String> initialNamespaceMap = CollectionFactory.newMap();
184
185        initialNamespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace");
186        initialNamespaceMap.put("xmlns", "http://www.w3.org/2000/xmlns/");
187
188        rootElement.toMarkup(document, writer, initialNamespaceMap);
189    }
190
191    public Element getRootElement()
192    {
193        return rootElement;
194    }
195
196    /**
197     * Tries to find an element in this document whose id is specified.
198     *
199     * @param id the value of the id attribute of the element being looked for
200     * @return the element if found. null if not found.
201     */
202    public Element getElementById(String id)
203    {
204        return rootElement.getElementById(id);
205    }
206
207    /**
208     * Sets the DTD for the document, overriding any prior DTD.
209     *
210     * @param name     non-blank name of document type (i.e., "html")
211     * @param publicId optional
212     * @param systemId optional
213     */
214    public void dtd(String name, String publicId, String systemId)
215    {
216        dtd = new DTD(name, publicId, systemId);
217    }
218
219    /**
220     * Returns true if the document has an explicit DTD (set via {@link #dtd(String, String, String)}).
221     *
222     * @since 5.3
223     */
224    public boolean hasDTD()
225    {
226        return dtd != null;
227    }
228
229    @Override
230    protected Map<String, String> getNamespaceURIToPrefix()
231    {
232        if (rootElement == null)
233        {
234            return Collections.emptyMap();
235        }
236
237        return rootElement.getNamespaceURIToPrefix();
238    }
239
240    /**
241     * Visits the root element of the document.
242     *
243     * @param visitor callback
244     * @since 5.1.0.0
245     */
246    void visit(Visitor visitor)
247    {
248        rootElement.visit(visitor);
249    }
250
251    private <T extends Node> T newChild(T child)
252    {
253        if (preamble == null)
254            preamble = CollectionFactory.newList();
255
256        preamble.add(child);
257
258        return child;
259    }
260
261    /**
262     * Adds the comment and returns this document for further construction.
263     *
264     * @since 5.1.0.0
265     */
266    public Document comment(String text)
267    {
268        newChild(new Comment(null, text));
269
270        return this;
271    }
272
273    /**
274     * Adds the raw text and returns this document for further construction.
275     *
276     * @since 5.1.0.0
277     */
278    public Document raw(String text)
279    {
280        newChild(new Raw(null, text));
281
282        return this;
283    }
284
285    /**
286     * Adds and returns a new text node (the text node is returned so that {@link Text#write(String)} or [@link
287     * {@link Text#writef(String, Object[])} may be invoked.
288     *
289     * @param text initial text for the node
290     * @return the new Text node
291     */
292    public Text text(String text)
293    {
294        return newChild(new Text(null, text));
295    }
296
297    /**
298     * Adds and returns a new CDATA node.
299     *
300     * @param content the content to be rendered by the node
301     * @return the newly created node
302     */
303    public CData cdata(String content)
304    {
305        return newChild(new CData(null, content));
306    }
307
308    /**
309     * Returns the MIME type of this document.
310     * @return the MIME type.
311     * @since 5.4
312     */
313    public String getMimeType()
314    {
315        return mimeType;
316    }
317    
318}