/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.util.xmlparser; import org.hyperic.util.StringUtil; import org.jdom.Attribute; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.File; import java.io.PrintStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.xml.sax.EntityResolver; /** * The main entry point && bulk of XmlParser. The parsing routine * takes an entry-point tag, which provides information about subtags, * attributes it takes, etc. Tags can implement various interfaces * to tell the parser to call back when certain conditions are met. * This class takes the role of both a minimal validator as well as * a traversal mechanism for building data objects out of XML. */ public class XmlParser { private XmlParser(){} private static void checkAttributes(Element elem, XmlTagHandler tag, XmlFilterHandler filter) throws XmlAttrException { boolean handlesAttrs = tag instanceof XmlAttrHandler; XmlAttr[] attrs; if(handlesAttrs) attrs = ((XmlAttrHandler)tag).getAttributes(); else attrs = new XmlAttr[0]; // Ensure out all the required && optional attributes for(int i=0; i<attrs.length; i++){ Attribute a = null; boolean found = false; for(Iterator j=elem.getAttributes().iterator(); j.hasNext(); ){ a = (Attribute)j.next(); if(a.getName().equalsIgnoreCase(attrs[i].getName())){ found = true; break; } } if(!found && attrs[i].getType() == XmlAttr.REQUIRED){ throw new XmlRequiredAttrException(elem, attrs[i].getName()); } if(found && handlesAttrs){ String val; val = filter.filterAttrValue(tag, a.getName(), a.getValue()); ((XmlAttrHandler)tag).handleAttribute(i, val); } } // Second loop to handle unknown attributes for(Iterator i=elem.getAttributes().iterator(); i.hasNext(); ){ Attribute a = (Attribute)i.next(); boolean found = false; for(int j=0; j<attrs.length; j++){ if(a.getName().equalsIgnoreCase(attrs[j].getName())){ found = true; break; } } if(found) continue; if(tag instanceof XmlUnAttrHandler){ XmlUnAttrHandler handler; String val; val = filter.filterAttrValue(tag, a.getName(), a.getValue()); handler = (XmlUnAttrHandler)tag; handler.handleUnknownAttribute(a.getName(), val); } else { throw new XmlUnknownAttrException(elem, a.getName()); } } if(tag instanceof XmlEndAttrHandler){ ((XmlEndAttrHandler)tag).endAttributes(); } } private static void checkSubNodes(Element elem, XmlTagHandler tag, XmlFilterHandler filter) throws XmlAttrException, XmlTagException { XmlTagInfo[] subTags = tag.getSubTags(); Map hash; hash = new HashMap(); // First, count how many times each sub-tag is referenced for(Iterator i=elem.getChildren().iterator(); i.hasNext(); ){ Element e = (Element)i.next(); String name; Integer val; name = e.getName().toLowerCase(); if((val = (Integer)hash.get(name)) == null){ val = new Integer(0); } val = new Integer(val.intValue() + 1); hash.put(name, val); } for(int i=0; i<subTags.length; i++){ String name = subTags[i].getTag().getName().toLowerCase(); Integer iVal = (Integer)hash.get(name); int threshold = 0, val; val = iVal == null ? 0 : iVal.intValue(); switch(subTags[i].getType()){ case XmlTagInfo.REQUIRED: if(val == 0){ throw new XmlMissingTagException(elem, name); } else if(val != 1){ throw new XmlTooManyTagException(elem, name); } break; case XmlTagInfo.OPTIONAL: if(val > 1){ throw new XmlTooManyTagException(elem, name); } break; case XmlTagInfo.ONE_OR_MORE: threshold++; case XmlTagInfo.ZERO_OR_MORE: if(val < threshold){ throw new XmlMissingTagException(elem, name); } break; } hash.remove(name); } // Now check for excess sub-tags if(hash.size() != 0){ Set keys = hash.keySet(); throw new XmlTooManyTagException(elem, (String)keys.iterator().next()); } // Recurse to all sub-tags for(Iterator i=elem.getChildren().iterator(); i.hasNext(); ){ Element child = (Element)i.next(); for(int j=0; j<subTags.length; j++){ XmlTagHandler subTag = subTags[j].getTag(); String subName = subTag.getName(); if(child.getName().equalsIgnoreCase(subName)){ XmlParser.processNode(child, subTag, filter); break; } } } } private static void processNode(Element elem, XmlTagHandler tag, XmlFilterHandler filter) throws XmlAttrException, XmlTagException { if(tag instanceof XmlTagEntryHandler){ ((XmlTagEntryHandler)tag).enter(); } if(tag instanceof XmlFilterHandler){ filter = (XmlFilterHandler)tag; } XmlParser.checkAttributes(elem, tag, filter); if(tag instanceof XmlTextHandler) { ((XmlTextHandler)tag).handleText(elem.getText()); } XmlParser.checkSubNodes(elem, tag, filter); if(tag instanceof XmlTagExitHandler){ ((XmlTagExitHandler)tag).exit(); } } private static class DummyFilter implements XmlFilterHandler { public String filterAttrValue(XmlTagHandler tag, String attrName, String attrValue) { return attrValue; } } /** * Parse an input stream, otherwise the same as parsing a file */ public static void parse(InputStream is, XmlTagHandler tag) throws XmlParseException { parse(is, tag, null); } public static void parse(InputStream is, XmlTagHandler tag, EntityResolver resolver) throws XmlParseException { SAXBuilder builder; Document doc; builder = new SAXBuilder(); if (resolver != null) { builder.setEntityResolver(resolver); } try { if (resolver != null) { //WTF? seems relative entity URIs are allowed //by certain xerces impls. but fully qualified //file://... URLs trigger a NullPointerException //in others. setting base here worksaround doc = builder.build(is, ""); } else { doc = builder.build(is); } } catch(JDOMException exc){ XmlParseException toThrow = new XmlParseException(exc.getMessage()); toThrow.initCause(exc); throw toThrow; } catch (IOException exc) { XmlParseException toThrow = new XmlParseException(exc.getMessage()); toThrow.initCause(exc); throw toThrow; } generalParse(tag, doc); } /** * Parse a file, which should have a root which is the associated tag. * * @param in File to parse * @param tag Root tag which the parsed file should contain */ public static void parse(File in, XmlTagHandler tag) throws XmlParseException { SAXBuilder builder; Document doc; builder = new SAXBuilder(); InputStream is = null; //open the file ourselves. the builder(File) //method escapes " " -> "%20" and bombs try { is = new FileInputStream(in); doc = builder.build(is); } catch (IOException exc) { throw new XmlParseException(exc.getMessage()); } catch (JDOMException exc) { throw new XmlParseException(exc.getMessage()); } finally { if (is != null) { try { is.close(); } catch (IOException e) {} } } generalParse(tag, doc); } /** General parsing used by both parse methods above */ private static void generalParse(XmlTagHandler tag, Document doc) throws XmlParseException { Element root = doc.getRootElement(); if(!root.getName().equalsIgnoreCase(tag.getName())){ throw new XmlParseException("Incorrect root tag. Expected <"+ tag.getName() + "> but got <" + root.getName() + ">"); } XmlParser.processNode(root, tag, new DummyFilter()); } private static void dumpAttrs(XmlAttr[] attrs, String typeName, int type, PrintStream out, int indent) { String printMsg; boolean printed = false; int lineBase, lineLen; if(attrs.length == 0) return; lineLen = 0; printMsg = "- Has " + typeName + " attributes: "; lineBase = indent + printMsg.length(); // Required attributes for(int i=0; i<attrs.length; i++){ String toPrint; if(attrs[i].getType() != type) continue; if(!printed){ toPrint = StringUtil.repeatChars(' ', indent) + "- Has " + typeName + " attributes: "; out.print(toPrint); lineLen = toPrint.length(); printed = true; } toPrint = attrs[i].getName() + ", "; lineLen += toPrint.length(); out.print(toPrint); if(lineLen > 70){ out.println(); out.print(StringUtil.repeatChars(' ', lineBase)); lineLen = lineBase; } } if(printed) out.println(); } private static void dumpNode(XmlTagHandler tag, PrintStream out, int indent) throws XmlTagException { XmlTagInfo[] subTags = tag.getSubTags(); out.println(StringUtil.repeatChars(' ', indent) + "Tag <" + tag.getName() + ">:"); if(tag instanceof XmlAttrHandler){ XmlAttr[] attrs; attrs = ((XmlAttrHandler)tag).getAttributes(); if(attrs.length == 0) out.println(StringUtil.repeatChars(' ', indent) + "- has no required or optional attributes"); XmlParser.dumpAttrs(attrs, "REQUIRED", XmlAttr.REQUIRED, out, indent); XmlParser.dumpAttrs(attrs, "OPTIONAL", XmlAttr.OPTIONAL, out, indent); } else { out.println(StringUtil.repeatChars(' ', indent) + "- has no required or optional attributes"); } if(tag instanceof XmlUnAttrHandler) out.println(StringUtil.repeatChars(' ', indent) + "- handles arbitrary attributes"); subTags = tag.getSubTags(); if(subTags.length == 0){ out.println(StringUtil.repeatChars(' ', indent) + "- has no subtags"); } else { for(int i=0; i<subTags.length; i++){ String name = subTags[i].getTag().getName(); int type = subTags[i].getType(); out.print(StringUtil.repeatChars(' ', indent) + "- has subtag <" + name + ">, which "); switch(type){ case XmlTagInfo.REQUIRED: out.println("is REQUIRED"); break; case XmlTagInfo.OPTIONAL: out.println("is OPTIONAL"); break; case XmlTagInfo.ONE_OR_MORE: out.println("is REQUIRED at least ONCE"); break; case XmlTagInfo.ZERO_OR_MORE: out.println("can be specified any # of times"); break; } XmlParser.dumpNode(subTags[i].getTag(), out, indent + 4); } } } public static void dump(XmlTagHandler root, PrintStream out){ try { XmlParser.dumpNode(root, out, 0); } catch(XmlTagException exc){ out.println("Error traversing tags: " + exc.getMessage()); } } private static String bold(String text) { return "<emphasis role=\"bold\">" + text + "</emphasis>"; } private static String tag(String name) { return bold("<" + name + ">"); } private static String listitem(String name, String desc) { String item = "<listitem><para>" + name + "</para>"; if (desc != null) { item += "<para>" + desc + "</para>"; } return item; } private static void dumpAttrsWiki(XmlAttr[] attrs, String typeName, int type, PrintStream out, int indent) { boolean printed = false; if (attrs.length == 0) { return; } // Required attributes for (int i=0; i<attrs.length; i++) { if (attrs[i].getType() != type) { continue; } if (!printed) { out.println(StringUtil.repeatChars('*', indent) + " " + typeName + " attributes: "); printed = true; } out.println(StringUtil.repeatChars('*', indent) + " " + attrs[i].getName()); } } private static void dumpNodeWiki(XmlTagHandler tag, PrintStream out, int indent) throws XmlTagException { XmlTagInfo[] subTags = tag.getSubTags(); if (indent == 1) { out.println(StringUtil.repeatChars('*', indent) + " Tag *<" + tag.getName() + ">*: "); } if (tag instanceof XmlAttrHandler) { XmlAttr[] attrs; attrs = ((XmlAttrHandler)tag).getAttributes(); dumpAttrsWiki(attrs, "*REQUIRED*", XmlAttr.REQUIRED, out, indent+1); dumpAttrsWiki(attrs, "*OPTIONAL*", XmlAttr.OPTIONAL, out, indent+1); } subTags = tag.getSubTags(); if (subTags.length != 0) { for (int i=0; i<subTags.length; i++) { String name = subTags[i].getTag().getName(); int type = subTags[i].getType(); String desc = ""; switch(type){ case XmlTagInfo.REQUIRED: desc = "REQUIRED"; break; case XmlTagInfo.OPTIONAL: desc = "OPTIONAL"; break; case XmlTagInfo.ONE_OR_MORE: desc = "REQUIRED at least ONCE"; break; case XmlTagInfo.ZERO_OR_MORE: desc = "can be specified any # of times"; break; } out.println(StringUtil.repeatChars('*', indent+1) + " Sub Tag *<" + name + ">* " + desc); dumpNodeWiki(subTags[i].getTag(), out, indent+1); } } } public static void dumpWiki(XmlTagHandler root, PrintStream out){ try { dumpNodeWiki(root, out, 1); } catch(XmlTagException exc){ out.println("Error traversing tags: " + exc.getMessage()); } } }