001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.util;
030
031import java.util.ArrayList;
032import java.util.List;
033
034import org.w3c.dom.Attr;
035import org.w3c.dom.Element;
036import org.w3c.dom.NamedNodeMap;
037import org.w3c.dom.Node;
038import org.w3c.dom.NodeList;
039
040/**
041 * A collection of utilities for XML..
042 *
043 */
044public abstract class XmlUtil extends ucar.unidata.xml.XmlUtil {
045
046    /**
047     * Print all the attributes of the given node
048     *
049     * @param parent
050     */
051    public static void printNode(Node parent) {
052        if (parent==null) {
053            System.out.println("null node!");
054            return;
055        }
056        System.out.println(parent.getNodeName() + " node:");
057        NamedNodeMap attrs = parent.getAttributes();
058        for(int i = 0 ; i<attrs.getLength() ; i++) {
059            Attr attribute = (Attr)attrs.item(i);
060            System.out.println("  " + attribute.getName()+" = "+attribute.getValue());
061        }
062    }
063
064    /**
065     *  Find all of the  descendant elements of the given parent Node
066     *  whose tag name.equals the given tag.
067     *
068     *  @param parent The root of the xml dom tree to search.
069     *  @param tag The tag name to match.
070     *  @return The list of descendants that match the given tag.
071     */
072    public static List<String> findDescendantNamesWithSeparator(Node parent, String tag, String separator) {
073        List<String> found = new ArrayList<>();
074        findDescendantNamesWithSeparator(parent, tag, "", separator, found);
075        return found;
076    }
077    
078    /**
079     *  Find all of the  descendant elements of the given parent Node
080     *  whose tag name equals the given tag.
081     *
082     *  @param parent The root of the xml dom tree to search.
083     *  @param tag The tag name to match.
084     *  @param found The list of descendants that match the given tag.
085     */
086    private static void findDescendantNamesWithSeparator(Node parent, String tag, String descendants, String separator, List<String> found) {
087            if (parent instanceof Element) {
088                String elementName = ((Element)parent).getAttribute("name");
089                if (!elementName.isEmpty()) {
090                    descendants += ((Element)parent).getAttribute("name");
091                }
092                if (parent.getNodeName().equals(tag)) {
093                    found.add(descendants);
094                }
095                if (!elementName.isEmpty()) {
096                    descendants += separator;
097                }
098        }
099        NodeList children = parent.getChildNodes();
100        for (int i = 0; i < children.getLength(); i++) {
101            Node child = children.item(i);
102            findDescendantNamesWithSeparator(child, tag, descendants, separator, found);
103        }
104    }
105    
106    /**
107     * Find the element described by nameList (path).
108     * 
109     * @param parent
110     * @param nameList
111     *
112     * @return {@code Element} described by the given path, or {@code null} if
113     * there was a problem.
114     */
115    public static Element getElementAtNamedPath(Node parent, List<String> nameList) {
116        return getMakeElementAtNamedPath(parent, nameList, "", false);
117    }
118    
119    /**
120     * Make the element described by nameList (path).
121     * 
122     * @param parent
123     * @param nameList
124     *
125     * @return {@code Element} described by the given path, or {@code null} if
126     * there was a problem.
127     */
128    public static Element makeElementAtNamedPath(Node parent, List<String> nameList, String tagname) {
129        return getMakeElementAtNamedPath(parent, nameList, tagname, true);
130    }
131    
132    /**
133     * Find the element described by nameList (path).
134     * 
135     * @param parent
136     * @param nameList
137     *
138     * @return {@code Element} described by the given path, or {@code null} if
139     * there was a problem.
140     */
141    public static Element getMakeElementAtNamedPath(Node parent, List<String> nameList, String tagName, boolean makeNew) {
142        Element thisElement = null;
143        if ((parent instanceof Element) && !nameList.isEmpty()) {
144            for (int i=0; i < nameList.size(); i++) {
145                String thisName = nameList.get(i);
146                NodeList children = parent.getChildNodes();
147                boolean foundChild = false;
148                for (int j = 0; j < children.getLength(); j++) {
149                    Node child = children.item(j);
150                    if (!(child instanceof Element)) {
151                        continue;
152                    }
153                    if (XmlUtil.getAttribute(child, "name").equals(thisName)) {
154                        if (i == (nameList.size() - 1)) {
155                            thisElement = (Element)child;
156                        }
157                        parent = child;
158                        foundChild = true;
159                        break;
160                    }
161                }
162
163                // Didn't find it where we expected to.  Create a new one.
164                if (makeNew && !foundChild && (parent instanceof Element)) {
165                    try {
166                        Element newElement = XmlUtil.create(tagName, (Element)parent);
167                        newElement.setAttribute("name", thisName);
168                        parent.appendChild(newElement);
169                        parent = newElement;
170                        thisElement = newElement;
171                    } catch (Exception ex) {
172                        System.err.println("Error making new " + tagName + " node named " + thisName);
173                        break;
174                    }
175                }
176            }
177        }
178        return thisElement;
179    }
180    
181    /**
182     * Added by TJJ Feb 2014
183     * 
184     * This method ensures that the output String has only
185     * valid XML unicode characters as specified by the
186     * XML 1.0 standard. For reference, please see
187     * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
188     * standard</a>. This method will return an empty
189     * String if the input is null or empty.
190     *
191     * @param in The String whose non-valid characters we want to remove.
192     * @return The in String, stripped of non-valid characters.
193     */
194    
195    public static String stripNonValidXMLCharacters(String in) {
196        if ((in == null) || in.isEmpty()) {
197            return ""; // vacancy test.
198        }
199        StringBuilder out = new StringBuilder(in.length()); // Used to hold the output.
200        for (int i = 0; i < in.length(); i++) {
201            char current = in.charAt(i); // Used to reference the current character.
202                                         // NOTE: No IndexOutOfBoundsException caught here; it should not happen.
203            if ((current == 0x9) ||
204                (current == 0xA) ||
205                (current == 0xD) ||
206                ((current >= 0x20) && (current <= 0xD7FF)) ||
207                ((current >= 0xE000) && (current <= 0xFFFD)) ||
208                ((current >= 0x10000) && (current <= 0x10FFFF)))
209            {
210                out.append(current);
211            }
212        }
213        return out.toString();
214    }
215    
216}