001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 * $Id$
020 */
021
022package org.nuxeo.common.xmap;
023
024import java.io.IOException;
025import java.io.StringReader;
026import java.util.Collection;
027import java.util.Map;
028
029import javax.xml.parsers.DocumentBuilderFactory;
030import javax.xml.parsers.ParserConfigurationException;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.w3c.dom.Document;
035import org.w3c.dom.DocumentFragment;
036import org.w3c.dom.Element;
037import org.w3c.dom.Node;
038import org.xml.sax.InputSource;
039import org.xml.sax.SAXException;
040
041/**
042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
043 */
044public final class DOMHelper {
045
046    private static final Log log = LogFactory.getLog(DOMHelper.class);
047
048    // Utility class.
049    private DOMHelper() {
050    }
051
052    /**
053     * Gets the value of the node at the given path relative to the given base element.
054     * <p>
055     * For element nodes the value is the text content and for the attributes node the attribute value.
056     *
057     * @return the node value or null if no such node was found
058     */
059    public static String getNodeValue(Element base, Path path) {
060        Node node = getElementNode(base, path);
061        if (node != null) {
062            if (path.attribute != null) {
063                Node at = node.getAttributes().getNamedItem(path.attribute);
064                return at != null ? at.getNodeValue() : null;
065            } else {
066                return node.getTextContent();
067            }
068        }
069        return null;
070    }
071
072    /**
073     * Visits the nodes selected by the given path using the given visitor.
074     */
075    public static void visitNodes(Context ctx, XAnnotatedList xam, Element base, Path path, NodeVisitor visitor,
076            Collection<Object> result) {
077        Node el = base;
078        int len = path.segments.length - 1;
079        for (int i = 0; i < len; i++) {
080            el = getElementNode(el, path.segments[i]);
081            if (el == null) {
082                return;
083            }
084        }
085        String name = path.segments[len];
086
087        if (path.attribute != null) {
088            visitAttributes(ctx, xam, el, name, path.attribute, visitor, result);
089        } else {
090            visitElements(ctx, xam, el, name, visitor, result);
091        }
092    }
093
094    public static void visitAttributes(Context ctx, XAnnotatedList xam, Node base, String name, String attrName,
095            NodeVisitor visitor, Collection<Object> result) {
096        Node p = base.getFirstChild();
097        while (p != null) {
098            if (p.getNodeType() == Node.ELEMENT_NODE) {
099                if (name.equals(p.getNodeName())) {
100                    Node at = p.getAttributes().getNamedItem(attrName);
101                    if (at != null) {
102                        visitor.visitNode(ctx, xam, at, result);
103                    }
104                }
105            }
106            p = p.getNextSibling();
107        }
108    }
109
110    public static void visitElements(Context ctx, XAnnotatedList xam, Node base, String name, NodeVisitor visitor,
111            Collection<Object> result) {
112        Node p = base.getFirstChild();
113        while (p != null) {
114            if (p.getNodeType() == Node.ELEMENT_NODE) {
115                if (name.equals(p.getNodeName())) {
116                    visitor.visitNode(ctx, xam, p, result);
117                }
118            }
119            p = p.getNextSibling();
120        }
121    }
122
123    public static void visitMapNodes(Context ctx, XAnnotatedMap xam, Element base, Path path, NodeMapVisitor visitor,
124            Map<String, Object> result) {
125        Node el = base;
126        int len = path.segments.length - 1;
127        for (int i = 0; i < len; i++) {
128            el = getElementNode(el, path.segments[i]);
129            if (el == null) {
130                return;
131            }
132        }
133        String name = path.segments[len];
134
135        if (path.attribute != null) {
136            visitMapAttributes(ctx, xam, el, name, path.attribute, visitor, result);
137        } else {
138            visitMapElements(ctx, xam, el, name, visitor, result);
139        }
140    }
141
142    public static void visitMapAttributes(Context ctx, XAnnotatedMap xam, Node base, String name, String attrName,
143            NodeMapVisitor visitor, Map<String, Object> result) {
144        Node p = base.getFirstChild();
145        while (p != null) {
146            if (p.getNodeType() == Node.ELEMENT_NODE) {
147                if (name.equals(p.getNodeName())) {
148                    Node at = p.getAttributes().getNamedItem(attrName);
149                    if (at != null) {
150                        String key = getNodeValue((Element) p, xam.key);
151                        if (key != null) {
152                            visitor.visitNode(ctx, xam, at, key, result);
153                        }
154                    }
155                }
156            }
157            p = p.getNextSibling();
158        }
159    }
160
161    public static void visitMapElements(Context ctx, XAnnotatedMap xam, Node base, String name, NodeMapVisitor visitor,
162            Map<String, Object> result) {
163        Node p = base.getFirstChild();
164        while (p != null) {
165            if (p.getNodeType() == Node.ELEMENT_NODE) {
166                if (name.equals(p.getNodeName())) {
167                    String key = getNodeValue((Element) p, xam.key);
168                    if (key != null) {
169                        visitor.visitNode(ctx, xam, p, key, result);
170                    }
171                }
172            }
173            p = p.getNextSibling();
174        }
175    }
176
177    /**
178     * Gets the first child element node having the given name.
179     */
180    public static Node getElementNode(Node base, String name) {
181        Node node = base.getFirstChild();
182        while (node != null) {
183            if (node.getNodeType() == Node.ELEMENT_NODE) {
184                if (name.equals(node.getNodeName())) {
185                    return node;
186                }
187            }
188            node = node.getNextSibling();
189        }
190        return null;
191    }
192
193    public static Node getElementNode(Node base, Path path) {
194        Node el = base;
195        int len = path.segments.length;
196        for (int i = 0; i < len; i++) {
197            el = getElementNode(el, path.segments[i]);
198            if (el == null) {
199                return null;
200            }
201        }
202        return el;
203    }
204
205    public interface NodeVisitor {
206
207        void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection<Object> result);
208
209    }
210
211    public interface NodeMapVisitor {
212
213        void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, Map<String, Object> result);
214
215    }
216
217    /**
218     * Parses a string containing XML and returns a DocumentFragment containing the nodes of the parsed XML.
219     */
220    public static void loadFragment(Element el, String fragment) {
221        // Wrap the fragment in an arbitrary element
222        fragment = "<fragment>" + fragment + "</fragment>";
223        try {
224            // Create a DOM builder and parse the fragment
225            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
226            Document d = factory.newDocumentBuilder().parse(new InputSource(new StringReader(fragment)));
227
228            Document doc = el.getOwnerDocument();
229
230            // Import the nodes of the new document into doc so that they
231            // will be compatible with doc
232            Node node = doc.importNode(d.getDocumentElement(), true);
233
234            // Create the document fragment node to hold the new nodes
235            DocumentFragment docfrag = doc.createDocumentFragment();
236
237            // Move the nodes into the fragment
238            while (node.hasChildNodes()) {
239                el.appendChild(node.removeChild(node.getFirstChild()));
240            }
241
242        } catch (ParserConfigurationException e) {
243            log.error(e, e);
244        } catch (SAXException e) {
245            log.error(e, e);
246        } catch (IOException e) {
247            log.error(e, e);
248        }
249    }
250
251}