    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    }