001    // Copyright 2006, 2007, 2008, 2009 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.Defense;
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            Defense.notNull(model, "model");
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            Defense.notBlank(path, "path");
084    
085            if (rootElement == null) return null;
086    
087            int slashx = path.indexOf("/");
088    
089            String rootElementName = slashx < 0 ? path : path.substring(0, slashx);
090    
091            if (!rootElement.getName().equals(rootElementName)) return null;
092    
093            return slashx < 0 ? rootElement : rootElement.find(path.substring(slashx + 1));
094        }
095    
096        /**
097         * Builds with an instance of {@link DefaultMarkupModel}.
098         */
099        public Document()
100        {
101            this(new DefaultMarkupModel());
102        }
103    
104        public MarkupModel getMarkupModel()
105        {
106            return model;
107        }
108    
109        /**
110         * Creates the root element for this document, replacing any previous root element.
111         */
112        public Element newRootElement(String name)
113        {
114            rootElement = new Element(this, null, name);
115    
116            return rootElement;
117        }
118    
119        /**
120         * Creates a new root element within a namespace.
121         *
122         * @param namespace URI of namespace containing the element
123         * @param name      name of element with namespace
124         * @return the root element
125         */
126        public Element newRootElement(String namespace, String name)
127        {
128            rootElement = new Element(this, namespace, name);
129    
130            return rootElement;
131        }
132    
133        @Override
134        public void toMarkup(Document document, PrintWriter writer, Map<String, String> namespaceURIToPrefix)
135        {
136            if (rootElement == null) throw new IllegalStateException(DomMessages.noRootElement());
137    
138    
139            if (model.isXML())
140            {
141                writer.print("<?xml version=\"1.0\"");
142    
143                if (encoding != null) 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            Map<String, String> initialNamespaceMap = CollectionFactory.newMap();
159    
160            initialNamespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace");
161            initialNamespaceMap.put("xmlns", "http://www.w3.org/2000/xmlns/");
162    
163            rootElement.toMarkup(document, writer, initialNamespaceMap);
164        }
165    
166        @Override
167        public String toString()
168        {
169            if (rootElement == null) return "[empty Document]";
170    
171            return super.toString();
172        }
173    
174        public Element getRootElement()
175        {
176            return rootElement;
177        }
178    
179        /**
180         * Tries to find an element in this document whose id is specified.
181         *
182         * @param id the value of the id attribute of the element being looked for
183         * @return the element if found. null if not found.
184         */
185        public Element getElementById(String id)
186        {
187            return rootElement.getElementById(id);
188        }
189    
190        public void dtd(String name, String publicId, String systemId)
191        {
192            dtd = new DTD(name, publicId, systemId);
193        }
194    
195        @Override
196        protected Map<String, String> getNamespaceURIToPrefix()
197        {
198            if (rootElement == null)
199            {
200                return Collections.emptyMap();
201            }
202    
203            return rootElement.getNamespaceURIToPrefix();
204        }
205    
206        /**
207         * Visits the root element of the document.
208         *
209         * @param visitor callback
210         * @since 5.1.0.0
211         */
212        void visit(Visitor visitor)
213        {
214            rootElement.visit(visitor);
215        }
216    
217        private <T extends Node> T newChild(T child)
218        {
219            if (preamble == null)
220                preamble = CollectionFactory.newList();
221    
222            preamble.add(child);
223    
224            return child;
225        }
226    
227        /**
228         * Adds the comment and returns this document for further construction.
229         *
230         * @since 5.1.0.0
231         */
232        public Document comment(String text)
233        {
234            newChild(new Comment(null, text));
235    
236            return this;
237        }
238    
239        /**
240         * Adds the raw text and returns this document for further construction.
241         *
242         * @since 5.1.0.0
243         */
244        public Document raw(String text)
245        {
246            newChild(new Raw(null, text));
247    
248            return this;
249        }
250    
251        /**
252         * Adds and returns a new text node (the text node is returned so that {@link Text#write(String)} or [@link {@link
253         * Text#writef(String, Object[])} may be invoked.
254         *
255         * @param text initial text for the node
256         * @return the new Text node
257         */
258        public Text text(String text)
259        {
260            return newChild(new Text(null, text));
261        }
262    
263        /**
264         * Adds and returns a new CDATA node.
265         *
266         * @param content the content to be rendered by the node
267         * @return the newly created node
268         */
269        public CData cdata(String content)
270        {
271            return newChild(new CData(null, content));
272        }
273    }