All Downloads are FREE. Search and download functionalities are using the official Maven repository.

netx.jnlp.Parser Maven / Gradle / Ivy

// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


package netx.jnlp;

import java.io.*;
import java.net.*;
import java.util.*;
//import javax.xml.parsers.*; // commented to use right Node
//import org.w3c.dom.*;       // class for using Tiny XML | NanoXML
//import org.xml.sax.*;
//import gd.xml.tiny.*;
import nanoxml.*;
import netx.jnlp.runtime.JNLPRuntime;


/**
 * Contains methods to parse an XML document into a JNLPFile.
 * Implements JNLP specification version 1.0.
 *
 * @author Jon A. Maxwell (JAM) - initial author
 * @version $Revision: 1.13 $ 
 */
class Parser {

    private static String R(String key) { return JNLPRuntime.getMessage(key); }
    private static String R(String key, Object p1) { return R(key, p1, null); }
    private static String R(String key, Object p1, Object p2) { return R(key, p1, p2, null); }
    private static String R(String key, Object p1, Object p2, Object p3) { return JNLPRuntime.getMessage(key, new Object[] { p1, p2, p3 }); }


    // defines netx.jnlp.Node class if using Tiny XML or Nano XML

    // Currently uses the Nano XML parse.  Search for "SAX" or
    // "TINY" or "NANO" and uncomment those blocks and comment the
    // active ones (if any) to switch XML parsers.  Also
    // (un)comment appropriate Node class at end of this file and
    // do a clean build.

    /**
     * Ensure consistent error handling.
     */
    /* SAX
    static ErrorHandler errorHandler = new ErrorHandler() {
        public void error(SAXParseException exception) throws SAXParseException {
            //throw exception;
        }
        public void fatalError(SAXParseException exception) throws SAXParseException {
            //throw exception;
        }
        public void warning(SAXParseException exception) {
            System.err.println("XML parse warning:");
            exception.printStackTrace();
        }
    };
    */


    /** the supported JNLP file versions */
    private static Version supportedVersions = new Version("1.0");

    // fix: some descriptors need to use the jnlp file at a later
    // date and having file ref lets us pass it to their
    // constructors
    //
    /** the file reference */
    private JNLPFile file; // do not use (uninitialized)

    /** the root node */
    private Node root;

    /** the specification version */
    private Version spec;

    /** the base URL that all hrefs are relative to */
    private URL base;

    /** the codebase URL */
    private URL codebase;

    /** the file URL */
    private URL fileLocation;

    /** whether to throw errors on non-fatal errors. */
    private boolean strict; // if strict==true parses a file with no error then strict==false should also

    /** whether to allow extensions to the JNLP specification */
    private boolean allowExtensions; // true if extensions to JNLP spec are ok


    /**
     * Create a parser for the JNLP file.  If the location
     * parameters is not null it is used as the default codebase
     * (does not override value of jnlp element's href
     * attribute).

* * The root node may be normalized as a side effect of this * constructor. * * @param file the (uninitialized) file reference * @param base if codebase is not specified, a default base for relative URLs * @param root the root node * @param strict whether to enforce strict compliance with the JNLP spec * @param allowExtensions whether to allow extensions to the JNLP spec * @throws ParseException if the JNLP file is invalid */ public Parser(JNLPFile file, URL base, Node root, boolean strict, boolean allowExtensions) throws ParseException { this.file = file; this.root = root; this.strict = strict; this.allowExtensions = allowExtensions; // ensure it's a JNLP node if (root == null || !root.getNodeName().equals("jnlp")) throw new ParseException(R("PInvalidRoot")); // JNLP tag information this.spec = getVersion(root, "spec", "1.0+"); this.codebase = addSlash(getURL(root, "codebase", base)); this.base = (codebase!=null) ? codebase : base; // if codebase not specified use default codebase fileLocation = getURL(root, "href", this.base); // ensure version is supported if (!supportedVersions.matchesAny(spec)) throw new ParseException(R("PSpecUnsupported", supportedVersions)); // normalize the text nodes root.normalize(); } /** * Return the JNLP specification versions supported. */ public static Version getSupportedVersions() { return supportedVersions; } /** * Returns the file version. */ public Version getFileVersion() { return getVersion(root, "version", null); } /** * Returns the file location. */ public URL getFileLocation() { return fileLocation; } /** * Returns the codebase. */ public URL getCodeBase() { return codebase; } /** * Returns the specification version. */ public Version getSpecVersion() { return spec; } // // This section loads the resources elements // /** * Returns all of the ResourcesDesc elements under the specified * node (jnlp or j2se). * * @param parent the parent node (either jnlp or j2se) * @param j2se true if the resources are located under a j2se node * @throws ParseException if the JNLP file is invalid */ public List getResources(Node parent, boolean j2se) throws ParseException { List result = new ArrayList(); Node resources[] = getChildNodes(parent, "resources"); // ensure that there are at least one information section present if (resources.length == 0 && !j2se) throw new ParseException(R("PNoResources")); // create objects from the resources sections for (int i=0; i < resources.length; i++) result.add(getResourcesDesc(resources[i], j2se)); return result; } /** * Returns the ResourcesDesc element at the specified node. * * @param node the resources node * @param j2se true if the resources are located under a j2se node * @throws ParseException if the JNLP file is invalid */ public ResourcesDesc getResourcesDesc(Node node, boolean j2se) throws ParseException { boolean mainFlag = false; // if found a main tag // create resources ResourcesDesc resources = new ResourcesDesc(file, getLocales(node), splitString(getAttribute(node, "os", null)), splitString(getAttribute(node, "arch", null))); // step through the elements Node child = node.getFirstChild(); while (child != null) { String name = child.getNodeName(); // check for nativelib but no trusted environment if ("nativelib".equals(name)) if (!isTrustedEnvironment()) throw new ParseException(R("PUntrustedNative")); if ("j2se".equals(name)) { if (getChildNode(root, "component-desc") != null) if (strict) throw new ParseException(R("PExtensionHasJ2SE")); if (!j2se) resources.addResource( getJRE(child) ); else throw new ParseException(R("PInnerJ2SE")); } if ("jar".equals(name) || "nativelib".equals(name)) { JARDesc jar = getJAR(child); // check for duplicate main entries if (jar.isMain()) { if (mainFlag == true) if (strict) throw new ParseException(R("PTwoMains")); mainFlag = true; } resources.addResource(jar); } if ("extension".equals(name)) resources.addResource( getExtension(child) ); if ("property".equals(name)) resources.addResource( getProperty(child) ); if ("package".equals(name)) resources.addResource( getPackage(child) ); child = child.getNextSibling(); } return resources; } /** * Returns the JRE element at the specified node. * * @param node the j2se node * @throws ParseException if the JNLP file is invalid */ public JREDesc getJRE(Node node) throws ParseException { Version version = getVersion(node, "version", null); URL location = getURL(node, "href", base); String initialHeap = getAttribute(node, "initial-heap-size", null); String maxHeap = getAttribute(node, "max-heap-size", null); List resources = getResources(node, true); // require version attribute getRequiredAttribute(node, "version", null); return new JREDesc(version, location, initialHeap, maxHeap, resources); } /** * Returns the JAR element at the specified node. * * @param node the jar or nativelib node * @throws ParseException if the JNLP file is invalid */ public JARDesc getJAR(Node node) throws ParseException { boolean nativeJar = "nativelib".equals(node.getNodeName()); URL location = getRequiredURL(node, "href", base); Version version = getVersion(node, "version", null); String part = getAttribute(node, "part", null); boolean main = "true".equals(getAttribute(node, "main", "false")); boolean lazy = "lazy".equals(getAttribute(node, "download", "eager")); int size = Integer.parseInt(getAttribute(node, "size", "0")); if (nativeJar && main) if (strict) throw new ParseException(R("PNativeHasMain")); return new JARDesc(location, version, part, lazy, main, nativeJar); } /** * Returns the Extension element at the specified node. * * @param node the extension node * @throws ParseException if the JNLP file is invalid */ public ExtensionDesc getExtension(Node node) throws ParseException { String name = getAttribute(node, "name", null); Version version = getVersion(node, "version", null); URL location = getRequiredURL(node, "href", base); ExtensionDesc ext = new ExtensionDesc(name, version, location); Node dload[] = getChildNodes(node, "ext-download"); for (int i=0; i < dload.length; i++) { boolean lazy = "lazy".equals(getAttribute(node, "download", "eager")); ext.addPart(getRequiredAttribute(node, "ext-part", null), getAttribute(node, "part", null), lazy); } return ext; } /** * Returns the Property element at the specified node. * * @param node the property node * @throws ParseException if the JNLP file is invalid */ public PropertyDesc getProperty(Node node) throws ParseException { String name = getRequiredAttribute(node, "name", null); String value = getRequiredAttribute(node, "value", ""); return new PropertyDesc(name, value); } /** * Returns the Package element at the specified node. * * @param node the package node * @throws ParseException if the JNLP file is invalid */ public PackageDesc getPackage(Node node) throws ParseException { String name = getRequiredAttribute(node, "name", null); String part = getRequiredAttribute(node, "part", ""); boolean recursive = getAttribute(node, "recursive", "false").equals("true"); return new PackageDesc(name, part, recursive); } // // This section loads the information elements // /** * Returns all of the information elements under the specified * node. * * @param parent the parent node (jnlp) * @throws ParseException if the JNLP file is invalid */ public List getInfo(Node parent) throws ParseException { List result = new ArrayList(); Node info[] = getChildNodes(parent, "information"); // ensure that there are at least one information section present if (info.length == 0) throw new ParseException(R("PNoInfoElement")); // create objects from the info sections for (int i=0; i < info.length; i++) result.add(getInformationDesc(info[i])); return result; } /** * Returns the information element at the specified node. * * @param node the information node * @throws ParseException if the JNLP file is invalid */ public InformationDesc getInformationDesc(Node node) throws ParseException { List descriptionsUsed = new ArrayList(); // locale Locale locales[] = getLocales(node); // create information InformationDesc info = new InformationDesc(file, locales); // step through the elements Node child = node.getFirstChild(); while (child != null) { String name = child.getNodeName(); if ("title".equals(name)) addInfo(info, child, null, getSpanText(child)); if ("vendor".equals(name)) addInfo(info, child, null, getSpanText(child)); if ("description".equals(name)) { String kind = getAttribute(child, "kind", "default"); if (descriptionsUsed.contains(kind)) if (strict) throw new ParseException(R("PTwoDescriptions", kind)); descriptionsUsed.add(kind); addInfo(info, child, kind, getSpanText(child)); } if ("homepage".equals(name)) addInfo(info, child, null, getRequiredURL(child, "href", base)); if ("icon".equals(name)) addInfo(info, child, getAttribute(child, "kind", "default"), getIcon(child)); if ("offline-allowed".equals(name)) addInfo(info, child, null, Boolean.TRUE); if ("sharing-allowed".equals(name)) { if (strict && !allowExtensions) throw new ParseException(R("PSharing")); addInfo(info, child, null, Boolean.TRUE); } child = child.getNextSibling(); } return info; } /** * Adds a key,value pair to the information object. * * @param info the information object * @param node node name to be used as the key * @param mod key name appended with "-"+mod if not null * @param value the info object to add (icon or string) */ protected void addInfo(InformationDesc info, Node node, String mod, Object value) { String modStr = (mod == null) ? "" : "-"+mod; if (node == null) return; info.addItem(node.getNodeName()+modStr, value); } /** * Returns the icon element at the specified node. * * @param node the icon node * @throws ParseException if the JNLP file is invalid */ public IconDesc getIcon(Node node) throws ParseException { int width = Integer.parseInt(getAttribute(node, "width", "-1")); int height = Integer.parseInt(getAttribute(node, "height", "-1")); int size = Integer.parseInt(getAttribute(node, "size", "-1")); int depth = Integer.parseInt(getAttribute(node, "depth", "-1")); URL location = getRequiredURL(node, "href", base); Object kind = getAttribute(node, "kind", "default"); return new IconDesc(location, kind, width, height, depth, size); } // // This section loads the security descriptor element // /** * Returns the security descriptor element. If no security * element was specified in the JNLP file then a SecurityDesc * with applet permissions is returned. * * @param parent the parent node * @throws ParseException if the JNLP file is invalid */ public SecurityDesc getSecurity(Node parent) throws ParseException { Node nodes[] = getChildNodes(parent, "security"); // test for too many security elements if (nodes.length > 1) if (strict) throw new ParseException(R("PTwoSecurity")); Object type = SecurityDesc.SANDBOX_PERMISSIONS; if (nodes.length == 0) type = SecurityDesc.SANDBOX_PERMISSIONS; else if (null != getChildNode(nodes[0], "all-permissions")) type = SecurityDesc.ALL_PERMISSIONS; else if (null != getChildNode(nodes[0], "j2ee-application-client-permissions")) type = SecurityDesc.J2EE_PERMISSIONS; else if (strict) throw new ParseException(R("PEmptySecurity")); if (base != null) return new SecurityDesc(file, type, base.getHost()); else return new SecurityDesc(file, type, null); } /** * Returns whether the JNLP file requests a trusted execution * environment. */ protected boolean isTrustedEnvironment() { Node security = getChildNode(root, "security"); if (security != null) if (getChildNode(security, "all-permissions") != null || getChildNode(security, "j2ee-application-client-permissions") != null) return true; return false; } // // This section loads the launch descriptor element // /** * Returns the launch descriptor element, either AppletDesc, * ApplicationDesc, ComponentDesc, or InstallerDesc. * * @param parent the parent node * @throws ParseException if the JNLP file is invalid */ public Object getLauncher(Node parent) throws ParseException { // check for other than one application type if (1 != getChildNodes(parent, "applet-desc").length + getChildNodes(parent, "application-desc").length + getChildNodes(parent, "component-desc").length + getChildNodes(parent, "installer-desc").length) throw new ParseException(R("PTwoDescriptors")); Node child = parent.getFirstChild(); while (child != null) { String name = child.getNodeName(); if ("applet-desc".equals(name)) return getApplet(child); if ("application-desc".equals(name)) return getApplication(child); if ("component-desc".equals(name)) return getComponent(child); if ("installer-desc".equals(name)) return getInstaller(child); child = child.getNextSibling(); } // not reached return null; } /** * Returns the applet descriptor. * * @throws ParseException if the JNLP file is invalid */ public AppletDesc getApplet(Node node) throws ParseException { String name = getRequiredAttribute(node, "name", R("PUnknownApplet")); String main = getRequiredAttribute(node, "main-class", null); URL docbase = getURL(node, "documentbase", base); Map paramMap = new HashMap(); int width = 0; int height = 0; try { width = Integer.parseInt(getRequiredAttribute(node, "width", "100")); height = Integer.parseInt(getRequiredAttribute(node, "height", "100")); } catch (NumberFormatException nfe) { if (width <= 0) throw new ParseException(R("PBadWidth")); throw new ParseException(R("PBadWidth")); } // read params Node params[] = getChildNodes(node, "param"); for (int i=0; i < params.length; i++) { paramMap.put(getRequiredAttribute(params[i], "name", null), getRequiredAttribute(params[i], "value", "")); } return new AppletDesc(name, main, docbase, width, height, paramMap); } /** * Returns the application descriptor. * * @throws ParseException if the JNLP file is invalid */ public ApplicationDesc getApplication(Node node) throws ParseException { String main = getAttribute(node, "main-class", null); List argsList = new ArrayList(); // if (main == null) // only ok if can be found in main jar file (can't check here but make a note) // read parameters Node args[] = getChildNodes(node, "argument"); for (int i=0; i < args.length; i++) { //argsList.add( args[i].getNodeValue() ); //This approach was not finding the argument text argsList.add( getSpanText(args[i]) ); } String argStrings[] = (String[]) argsList.toArray( new String[argsList.size()] ); return new ApplicationDesc(main, argStrings); } /** * Returns the component descriptor. */ public ComponentDesc getComponent(Node node) { return new ComponentDesc(); } /** * Returns the installer descriptor. */ public InstallerDesc getInstaller(Node node) { String main = getAttribute(node, "main-class", null); return new InstallerDesc(main); } // other methods /** * Returns an array of substrings seperated by spaces (spaces * escaped with backslash do not separate strings). This method * splits strings as per the spec except that it does replace * escaped other characters with their own value. */ public String[] splitString(String source) { if (source == null) return new String[0]; List result = new ArrayList(); StringTokenizer st = new StringTokenizer(source, " "); StringBuffer part = new StringBuffer(); while (st.hasMoreTokens()) { part.setLength(0); // tack together tokens joined by backslash while (true) { part.append(st.nextToken()); if (st.hasMoreTokens() && part.charAt(part.length()-1) == '\\') part.setCharAt(part.length()-1, ' '); // join with the space else break; // bizarre while format gets \ at end of string right (no extra space added at end) } // delete \ quote chars for (int i = part.length(); i-- > 0;) // sweet syntax for reverse loop if (part.charAt(i) == '\\') part.deleteCharAt(i--); // and skip previous char so \\ becomes \ result.add( part.toString() ); } return (String[]) result.toArray(new String[result.size()] ); } /** * Returns the Locale object(s) from a node's locale attribute. * * @param node the node with a locale attribute */ public Locale[] getLocales(Node node) { List locales = new ArrayList(); String localeParts[] = splitString(getAttribute(node, "locale", "")); for (int i=0; i < localeParts.length; i++) { Locale l = getLocale( localeParts[i] ); if (l != null) locales.add(l); } return (Locale[]) locales.toArray(new Locale[locales.size()] ); } /** * Returns a Locale from a single locale. * * @param locale the locale string */ public Locale getLocale(String localeStr) { if (localeStr.length() < 2) return null; String language = localeStr.substring(0, 2); String country = (localeStr.length()<5) ? "" : localeStr.substring(3, 5); String variant = (localeStr.length()<7) ? "" : localeStr.substring(6, 8); // null is not allowed n locale but "" is return new Locale(language, country, variant); } // XML junk /** * Returns the implied text under a node, for example "text" in * "text". * * @param node the node with text under it * @throws ParseException if the JNLP file is invalid */ public String getSpanText(Node node) throws ParseException { if (node == null) return null; // NANO return node.getNodeValue(); /* TINY Node child = node.getFirstChild(); if (child == null) { if (strict) // not sure if this is an error or whether "" is proper throw new ParseException("No text specified (node="+node.getNodeName()+")"); else return ""; } return child.getNodeValue(); */ } /** * Returns the first child node with the specified name. */ public static Node getChildNode(Node node, String name) { Node[] result = getChildNodes(node, name); if (result.length == 0) return null; else return result[0]; } /** * Returns all child nodes with the specified name. */ public static Node[] getChildNodes(Node node, String name) { List result = new ArrayList(); Node child = node.getFirstChild(); while (child != null) { if (child.getNodeName().equals(name)) result.add(child); child = child.getNextSibling(); } return (Node[]) result.toArray( new Node[result.size()] ); } /** * Returns a URL with a trailing / appended to it if there is no * trailing slash on the specifed URL. */ private URL addSlash(URL source) { if (source == null) return null; if (!source.toString().endsWith("/")) { try { source = new URL(source.toString()+"/"); } catch (MalformedURLException ex) { } } return source; } /** * Returns the same result as getURL except that a * ParseException is thrown if the attribute is null or empty. * * @param node the node * @param name the attribute containing an href * @param base the base URL * @throws ParseException if the JNLP file is invalid */ public URL getRequiredURL(Node node, String name, URL base) throws ParseException { // probably should change "" to null so that url is always // required even if !strict getRequiredAttribute(node, name, ""); return getURL(node, name, base); } /** * Returns a URL object from a href string relative to the * code base. If the href denotes a relative URL, it must * reference a location that is a subdirectory of the * codebase.

* * @param node the node * @param name the attribute containing an href * @param base the base URL * @throws ParseException if the JNLP file is invalid */ public URL getURL(Node node, String name, URL base) throws ParseException { String href = getAttribute(node, name, null); if (href == null) return null; // so that code can throw an exception if attribute was required try { if (base == null) return new URL(href); else { try { return new URL(href); } catch (MalformedURLException ex) { // is relative } URL result = new URL(base, href); // check for going above the codebase if (! result.toString().startsWith( base.toString()) ) if (strict) throw new ParseException(R("PUrlNotInCodebase", node.getNodeName(), href, base)); return result; } } catch (MalformedURLException ex) { if (base == null) throw new ParseException(R("PBadNonrelativeUrl", node.getNodeName(), href)); else throw new ParseException(R("PBadRelativeUrl", node.getNodeName(), href, base)); } } /** * Returns a Version from the specified attribute and default * value. * * @param node the node * @param name the attribute * @param defaultValue default if no such attribute * @return a Version, or null if no such attribute and default is null */ public Version getVersion(Node node, String name, String defaultValue) { String version = getAttribute(node, name, defaultValue); if (version == null) return null; else return new Version(version); } /** * Returns the same result as getAttribute except that if strict * mode is enabled or the default value is null a parse * exception is thrown instead of returning the default value. * * @param node the node * @param name the attribute * @param defaultValue default value * @throws ParseException if the attribute does not exist or is empty */ public String getRequiredAttribute(Node node, String name, String defaultValue) throws ParseException { String result = getAttribute(node, name, null); if (result == null || result.length() == 0) if (strict || defaultValue == null) throw new ParseException(R("PNeedsAttribute", node.getNodeName(), name)); if (result == null) return defaultValue; else return result; } /** * Retuns an attribute or the specified defaultValue if there is * no such attribute. * * @param node the node * @param name the attribute * @param defaultValue default if no such attribute */ public String getAttribute(Node node, String name, String defaultValue) { // SAX // String result = ((Element) node).getAttribute(name); String result = node.getAttribute(name); if (result == null || result.length()==0) return defaultValue; return result; } /** * Return the root node from the XML document in the specified * input stream. * * @throws ParseException if the JNLP file is invalid */ public static Node getRootNode(InputStream input) throws ParseException { try { /* SAX DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setErrorHandler(errorHandler); Document doc = builder.parse(input); return doc.getDocumentElement(); */ /* TINY Node document = new Node(TinyParser.parseXML(input)); Node jnlpNode = getChildNode(document, "jnlp"); // skip comments */ /* NANO */ XMLElement xml = new XMLElement(); xml.parseFromReader(new InputStreamReader(input)); Node jnlpNode = new Node(xml); return jnlpNode; } catch(Exception ex) { throw new ParseException(R("PBadXML"), ex); } } } // this class makes assumptions on how parser calls methods (such // as getFirstChild->getNextChild only called by a single loop at // a time, so no need for an iterator). /** * This class converts the NanoXML's XMLElement nodes into the * regular XML Node interface (for the methods used by Parser). */ /* NANO */ class Node { private XMLElement xml; private Node next; private Node children[]; Node(XMLElement xml) { this.xml = xml; } Node getFirstChild() { if (children == null) getChildNodes(); if (children.length == 0) return null; else return children[0]; } Node getNextSibling() { return next; } void normalize() { } String getNodeValue() { return xml.getContent(); } Node[] getChildNodes() { if (children == null) { List list = new ArrayList(); for (Enumeration e = xml.enumerateChildren(); e.hasMoreElements();) list.add( new Node((XMLElement)e.nextElement()) ); children = (Node[]) list.toArray( new Node[list.size()] ); for (int i=0; i < children.length-1; i++) children[i].next = children[i+1]; } return children; } String getAttribute(String name) { return (String)xml.getAttribute(name); } String getNodeName() { if (xml.getName() == null) return ""; else return xml.getName(); } public String toString() { return getNodeName(); } } /** * This class converts the TinyXML's ParsedXML nodes into the * regular XML Node interface (for the methods used by Parser). */ /* TINY class Node { private ParsedXML tinyNode; private Node next; private Node children[]; Node(ParsedXML tinyNode) { this.tinyNode = tinyNode; } Node getFirstChild() { if (children == null) getChildNodes(); if (children.length == 0) return null; else return children[0]; } Node getNextSibling() { return next; } void normalize() { } String getNodeValue() { return tinyNode.getContent(); } Node[] getChildNodes() { if (children == null) { List list = new ArrayList(); for (Enumeration e = tinyNode.elements(); e.hasMoreElements();) { list.add( new Node((ParsedXML)e.nextElement()) ); } children = (Node[]) list.toArray( new Node[list.size()] ); for (int i=0; i < children.length-1; i++) children[i].next = children[i+1]; } return children; } String getAttribute(String name) { return tinyNode.getAttribute(name); } String getNodeName() { if (tinyNode.getName() == null) return ""; else return tinyNode.getName(); } public String toString() { return getNodeName(); } } */





© 2015 - 2025 Weber Informatics LLC | Privacy Policy