001    // Copyright 2006, 2007, 2008, 2009, 2010 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    
015    package org.apache.tapestry5.dom;
016    
017    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
018    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
019    
020    import java.io.PrintWriter;
021    import java.util.Collections;
022    import java.util.List;
023    import java.util.Map;
024    
025    /**
026     * The root node of a DOM.
027     */
028    public 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        /**
049         * Non-element content that comes between the DOCTYPE and the root element.
050         */
051        private List<Node> preamble;
052    
053        public Document(MarkupModel model)
054        {
055            this(model, null);
056        }
057    
058        public Document(MarkupModel model, String encoding)
059        {
060            super(null);
061    
062            assert model != null;
063    
064            this.model = model;
065            this.encoding = encoding;
066        }
067    
068        @Override
069        public Document getDocument()
070        {
071            return this;
072        }
073    
074        /**
075         * Finds an element based on a path of element names.
076         *
077         * @param path slash separated series of element names
078         * @return the matching element, or null if not found
079         * @see Element#find(String)
080         */
081        public Element find(String path)
082        {
083            assert InternalUtils.isNonBlank(path);
084    
085            if (rootElement == null)
086                return null;
087    
088            int slashx = path.indexOf("/");
089    
090            String rootElementName = slashx < 0 ? path : path.substring(0, slashx);
091    
092            if (!rootElement.getName().equals(rootElementName))
093                return null;
094    
095            return slashx < 0 ? rootElement : rootElement.find(path.substring(slashx + 1));
096        }
097    
098        /**
099         * Builds with an instance of {@link DefaultMarkupModel}.
100         */
101        public Document()
102        {
103            this(new DefaultMarkupModel());
104        }
105    
106        public MarkupModel getMarkupModel()
107        {
108            return model;
109        }
110    
111        /**
112         * Creates the root element for this document, replacing any previous root element.
113         */
114        public Element newRootElement(String name)
115        {
116            rootElement = new Element(this, null, name);
117    
118            return rootElement;
119        }
120    
121        /**
122         * Creates a new root element within a namespace.
123         *
124         * @param namespace URI of namespace containing the element
125         * @param name      name of element with namespace
126         * @return the root element
127         */
128        public Element newRootElement(String namespace, String name)
129        {
130            rootElement = new Element(this, namespace, name);
131    
132            return rootElement;
133        }
134    
135        @Override
136        public void toMarkup(Document document, PrintWriter writer, Map<String, String> namespaceURIToPrefix)
137        {
138            if (model.isXML())
139            {
140                writer.print("<?xml version=\"1.0\"");
141    
142                if (encoding != null)
143                    writer.printf(" encoding=\"%s\"", encoding);
144    
145                writer.print("?>\n");
146            }
147            if (dtd != null)
148            {
149                dtd.toMarkup(writer);
150            }
151    
152            if (preamble != null)
153            {
154                for (Node n : preamble)
155                    n.toMarkup(this, writer, namespaceURIToPrefix);
156            }
157    
158            if (rootElement == null)
159                return;
160    
161            Map<String, String> initialNamespaceMap = CollectionFactory.newMap();
162    
163            initialNamespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace");
164            initialNamespaceMap.put("xmlns", "http://www.w3.org/2000/xmlns/");
165    
166            rootElement.toMarkup(document, writer, initialNamespaceMap);
167        }
168    
169        public Element getRootElement()
170        {
171            return rootElement;
172        }
173    
174        /**
175         * Tries to find an element in this document whose id is specified.
176         *
177         * @param id the value of the id attribute of the element being looked for
178         * @return the element if found. null if not found.
179         */
180        public Element getElementById(String id)
181        {
182            return rootElement.getElementById(id);
183        }
184    
185        /**
186         * Sets the DTD for the document, overriding any prior DTD.
187         *
188         * @param name     non-blank name of document type (i.e., "html")
189         * @param publicId optional
190         * @param systemId optional
191         */
192        public void dtd(String name, String publicId, String systemId)
193        {
194            dtd = new DTD(name, publicId, systemId);
195        }
196    
197        /**
198         * Returns true if the document has an explicit DTD (set via {@link #dtd(String, String, String)}).
199         *
200         * @since 5.3
201         */
202        public boolean hasDTD()
203        {
204            return dtd != null;
205        }
206    
207        @Override
208        protected Map<String, String> getNamespaceURIToPrefix()
209        {
210            if (rootElement == null)
211            {
212                return Collections.emptyMap();
213            }
214    
215            return rootElement.getNamespaceURIToPrefix();
216        }
217    
218        /**
219         * Visits the root element of the document.
220         *
221         * @param visitor callback
222         * @since 5.1.0.0
223         */
224        void visit(Visitor visitor)
225        {
226            rootElement.visit(visitor);
227        }
228    
229        private <T extends Node> T newChild(T child)
230        {
231            if (preamble == null)
232                preamble = CollectionFactory.newList();
233    
234            preamble.add(child);
235    
236            return child;
237        }
238    
239        /**
240         * Adds the comment and returns this document for further construction.
241         *
242         * @since 5.1.0.0
243         */
244        public Document comment(String text)
245        {
246            newChild(new Comment(null, text));
247    
248            return this;
249        }
250    
251        /**
252         * Adds the raw text and returns this document for further construction.
253         *
254         * @since 5.1.0.0
255         */
256        public Document raw(String text)
257        {
258            newChild(new Raw(null, text));
259    
260            return this;
261        }
262    
263        /**
264         * Adds and returns a new text node (the text node is returned so that {@link Text#write(String)} or [@link
265         * {@link Text#writef(String, Object[])} may be invoked.
266         *
267         * @param text initial text for the node
268         * @return the new Text node
269         */
270        public Text text(String text)
271        {
272            return newChild(new Text(null, text));
273        }
274    
275        /**
276         * Adds and returns a new CDATA node.
277         *
278         * @param content the content to be rendered by the node
279         * @return the newly created node
280         */
281        public CData cdata(String content)
282        {
283            return newChild(new CData(null, content));
284        }
285    }