001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://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 */
043public abstract class XmlUtil extends ucar.unidata.xml.XmlUtil {
044    
045    /**
046     * Print all the attributes of the given node
047     *
048     * @param parent Node whose attributes will be printed.
049     */
050    public static void printNode(Node parent) {
051        if (parent == null) {
052            System.out.println("null node!");
053            return;
054        }
055        System.out.println(parent.getNodeName() + " node:");
056        NamedNodeMap attrs = parent.getAttributes();
057        for(int i = 0 ; i < attrs.getLength() ; i++) {
058            Attr attribute = (Attr)attrs.item(i);
059            System.out.println("  " + attribute.getName()+" = "+attribute.getValue());
060        }
061    }
062
063    /**
064     * Find all of the  descendant elements of the given parent Node whose tag
065     * name equals the given tag.
066     *
067     * @param parent Root of the XML DOM tree to search.
068     * @param tag Tag name to match.
069     * @param separator String that separates tags into components.
070     *
071     * @return List of descendants that match the given tag.
072     */
073    public static List<String> findDescendantNamesWithSeparator(Node parent, String tag, String separator) {
074        List<String> found = new ArrayList<>();
075        findDescendantNamesWithSeparator(parent, tag, "", separator, found);
076        return found;
077    }
078
079    /**
080     * Find all of the descendant elements of the given parent Node whose
081     * tag name equals the given tag.
082     *
083     * @param parent Root of the XML DOM tree to search.
084     * @param tag Tag name to match.
085     * @param descendants Descendant elements.
086     * @param separator String separating tag components (also in descendants).
087     * @param found List of descendants that match the given tag.
088     */
089    private static void findDescendantNamesWithSeparator(Node parent, String tag, String descendants, String separator, List<String> found) {
090            if (parent instanceof Element) {
091                String elementName = ((Element)parent).getAttribute("name");
092                if (!elementName.isEmpty()) {
093                    descendants += ((Element)parent).getAttribute("name");
094                }
095                if (parent.getNodeName().equals(tag)) {
096                    found.add(descendants);
097                }
098                if (!elementName.isEmpty()) {
099                    descendants += separator;
100                }
101        }
102        NodeList children = parent.getChildNodes();
103        for (int i = 0; i < children.getLength(); i++) {
104            Node child = children.item(i);
105            findDescendantNamesWithSeparator(child, tag, descendants, separator, found);
106        }
107    }
108
109    /**
110     * Find the element described by nameList (path).
111     * 
112     * @param parent Node at which the search should begin.
113     * @param nameList List of node names to search for (think xpath).
114     *
115     * @return {@code Element} described by the given path, or {@code null} if
116     * there was a problem.
117     */
118    public static Element getElementAtNamedPath(Node parent, List<String> nameList) {
119        return getMakeElementAtNamedPath(parent, nameList, "", false);
120    }
121    
122    /**
123     * Make the element described by nameList (path).
124     * 
125     * @param parent Node at which the search should begin.
126     * @param nameList List of node names to search for (think xpath).
127     * @param tagName Tag name to locate.
128     *
129     * @return {@code Element} described by the given path, or {@code null} if
130     * there was a problem.
131     */
132    public static Element makeElementAtNamedPath(Node parent, List<String> nameList, String tagName) {
133        return getMakeElementAtNamedPath(parent, nameList, tagName, true);
134    }
135    
136    /**
137     * Find the element described by nameList (path).
138     * 
139     * @param parent Node at which the search should begin.
140     * @param nameList List of node names to search for (think xpath).
141     * @param tagName Tag name to locate.
142     * @param makeNew Whether or not a new {@code Element} should be created.
143     *
144     * @return {@code Element} described by the given path, or {@code null} if
145     * there was a problem.
146     */
147    public static Element getMakeElementAtNamedPath(Node parent, List<String> nameList, String tagName, boolean makeNew) {
148        Element thisElement = null;
149        if ((parent instanceof Element) && !nameList.isEmpty()) {
150            for (int i=0; i < nameList.size(); i++) {
151                String thisName = nameList.get(i);
152                NodeList children = parent.getChildNodes();
153                boolean foundChild = false;
154                for (int j = 0; j < children.getLength(); j++) {
155                    Node child = children.item(j);
156                    if (!(child instanceof Element)) {
157                        continue;
158                    }
159                    if (XmlUtil.getAttribute(child, "name").equals(thisName)) {
160                        if (i == (nameList.size() - 1)) {
161                            thisElement = (Element)child;
162                        }
163                        parent = child;
164                        foundChild = true;
165                        break;
166                    }
167                }
168
169                // Didn't find it where we expected to.  Create a new one.
170                if (makeNew && !foundChild && (parent instanceof Element)) {
171                    try {
172                        Element newElement = XmlUtil.create(tagName, (Element)parent);
173                        newElement.setAttribute("name", thisName);
174                        parent.appendChild(newElement);
175                        parent = newElement;
176                        thisElement = newElement;
177                    } catch (Exception ex) {
178                        System.err.println("Error making new " + tagName + " node named " + thisName);
179                        break;
180                    }
181                }
182            }
183        }
184        return thisElement;
185    }
186
187    /**
188     * This method ensures that the output String has only valid XML unicode
189     * characters as specified by the XML 1.0 standard.
190     *
191     * <p>For reference, please see
192     * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
193     * standard</a>. This method will return an empty
194     * String if the input is null or empty.
195     *
196     * @param in String whose non-valid characters we want to remove.
197     *
198     * @return The in String, stripped of non-valid characters.
199     */
200    // Added by TJJ Feb 2014
201    public static String stripNonValidXMLCharacters(String in) {
202        if ((in == null) || in.isEmpty()) {
203            return ""; // vacancy test.
204        }
205        StringBuilder out = new StringBuilder(in.length()); // Used to hold the output.
206        for (int i = 0; i < in.length(); i++) {
207            char current = in.charAt(i); // Used to reference the current character.
208                                         // NOTE: No IndexOutOfBoundsException caught here; it should not happen.
209            if ((current == 0x9) ||
210                (current == 0xA) ||
211                (current == 0xD) ||
212                ((current >= 0x20) && (current <= 0xD7FF)) ||
213                ((current >= 0xE000) && (current <= 0xFFFD)) ||
214                ((current >= 0x10000) && (current <= 0x10FFFF)))
215            {
216                out.append(current);
217            }
218        }
219        return out.toString();
220    }
221    
222}