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 }