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 }