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 }