001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
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.Element;
036import org.w3c.dom.Node;
037import org.xml.sax.InputSource;
038import org.xml.sax.SAXException;
039
040/**
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public final class DOMHelper {
044
045    private static final Log log = LogFactory.getLog(DOMHelper.class);
046
047    // Utility class.
048    private DOMHelper() {
049    }
050
051    /**
052     * Gets the value of the node at the given path relative to the given base element.
053     * <p>
054     * For element nodes the value is the text content and for the attributes node the attribute value.
055     *
056     * @return the node value or null if no such node was found
057     */
058    public static String getNodeValue(Element base, Path path) {
059        Node node = getElementNode(base, path);
060        if (node != null) {
061            if (path.attribute != null) {
062                Node at = node.getAttributes().getNamedItem(path.attribute);
063                return at != null ? at.getNodeValue() : null;
064            } else {
065                return node.getTextContent();
066            }
067        }
068        return null;
069    }
070
071    /**
072     * Visits the nodes selected by the given path using the given visitor.
073     */
074    public static void visitNodes(Context ctx, XAnnotatedList xam, Element base, Path path, NodeVisitor visitor,
075            Collection<Object> result) {
076        Node el = base;
077        int len = path.segments.length - 1;
078        for (int i = 0; i < len; i++) {
079            el = getElementNode(el, path.segments[i]);
080            if (el == null) {
081                return;
082            }
083        }
084        String name = path.segments[len];
085
086        if (path.attribute != null) {
087            visitAttributes(ctx, xam, el, name, path.attribute, visitor, result);
088        } else {
089            visitElements(ctx, xam, el, name, visitor, result);
090        }
091    }
092
093    public static void visitAttributes(Context ctx, XAnnotatedList xam, Node base, String name, String attrName,
094            NodeVisitor visitor, Collection<Object> result) {
095        Node p = base.getFirstChild();
096        while (p != null) {
097            if (p.getNodeType() == Node.ELEMENT_NODE) {
098                if (name.equals(p.getNodeName())) {
099                    Node at = p.getAttributes().getNamedItem(attrName);
100                    if (at != null) {
101                        visitor.visitNode(ctx, xam, at, result);
102                    }
103                }
104            }
105            p = p.getNextSibling();
106        }
107    }
108
109    public static void visitElements(Context ctx, XAnnotatedList xam, Node base, String name, NodeVisitor visitor,
110            Collection<Object> result) {
111        Node p = base.getFirstChild();
112        while (p != null) {
113            if (p.getNodeType() == Node.ELEMENT_NODE) {
114                if (name.equals(p.getNodeName())) {
115                    visitor.visitNode(ctx, xam, p, result);
116                }
117            }
118            p = p.getNextSibling();
119        }
120    }
121
122    public static void visitMapNodes(Context ctx, XAnnotatedMap xam, Element base, Path path, NodeMapVisitor visitor,
123            Map<String, Object> result) {
124        Node el = base;
125        int len = path.segments.length - 1;
126        for (int i = 0; i < len; i++) {
127            el = getElementNode(el, path.segments[i]);
128            if (el == null) {
129                return;
130            }
131        }
132        String name = path.segments[len];
133
134        if (path.attribute != null) {
135            visitMapAttributes(ctx, xam, el, name, path.attribute, visitor, result);
136        } else {
137            visitMapElements(ctx, xam, el, name, visitor, result);
138        }
139    }
140
141    public static void visitMapAttributes(Context ctx, XAnnotatedMap xam, Node base, String name, String attrName,
142            NodeMapVisitor visitor, Map<String, Object> result) {
143        Node p = base.getFirstChild();
144        while (p != null) {
145            if (p.getNodeType() == Node.ELEMENT_NODE) {
146                if (name.equals(p.getNodeName())) {
147                    Node at = p.getAttributes().getNamedItem(attrName);
148                    if (at != null) {
149                        String key = getNodeValue((Element) p, xam.key);
150                        if (key != null) {
151                            visitor.visitNode(ctx, xam, at, key, result);
152                        }
153                    }
154                }
155            }
156            p = p.getNextSibling();
157        }
158    }
159
160    public static void visitMapElements(Context ctx, XAnnotatedMap xam, Node base, String name, NodeMapVisitor visitor,
161            Map<String, Object> result) {
162        Node p = base.getFirstChild();
163        while (p != null) {
164            if (p.getNodeType() == Node.ELEMENT_NODE) {
165                if (name.equals(p.getNodeName())) {
166                    String key = getNodeValue((Element) p, xam.key);
167                    if (key != null) {
168                        visitor.visitNode(ctx, xam, p, key, result);
169                    }
170                }
171            }
172            p = p.getNextSibling();
173        }
174    }
175
176    /**
177     * Gets the first child element node having the given name.
178     */
179    public static Node getElementNode(Node base, String name) {
180        Node node = base.getFirstChild();
181        while (node != null) {
182            if (node.getNodeType() == Node.ELEMENT_NODE) {
183                if (name.equals(node.getNodeName())) {
184                    return node;
185                }
186            }
187            node = node.getNextSibling();
188        }
189        return null;
190    }
191
192    public static Node getElementNode(Node base, Path path) {
193        Node el = base;
194        int len = path.segments.length;
195        for (int i = 0; i < len; i++) {
196            el = getElementNode(el, path.segments[i]);
197            if (el == null) {
198                return null;
199            }
200        }
201        return el;
202    }
203
204    public interface NodeVisitor {
205
206        void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection<Object> result);
207
208    }
209
210    public interface NodeMapVisitor {
211
212        void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, Map<String, Object> result);
213
214    }
215
216    /**
217     * Parses a string containing XML and returns a DocumentFragment containing the nodes of the parsed XML.
218     */
219    public static void loadFragment(Element el, String fragment) {
220        // Wrap the fragment in an arbitrary element
221        fragment = "<fragment>" + fragment + "</fragment>";
222        try {
223            // Create a DOM builder and parse the fragment
224            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
225            Document d = factory.newDocumentBuilder().parse(new InputSource(new StringReader(fragment)));
226
227            Document doc = el.getOwnerDocument();
228
229            // Import the nodes of the new document into doc so that they
230            // will be compatible with doc
231            Node node = doc.importNode(d.getDocumentElement(), true);
232
233            // Create the document fragment node to hold the new nodes
234            doc.createDocumentFragment();
235
236            // Move the nodes into the fragment
237            while (node.hasChildNodes()) {
238                el.appendChild(node.removeChild(node.getFirstChild()));
239            }
240
241        } catch (ParserConfigurationException e) {
242            log.error(e, e);
243        } catch (SAXException e) {
244            log.error(e, e);
245        } catch (IOException e) {
246            log.error(e, e);
247        }
248    }
249
250}