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.commons.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}