package jdiff; import java.io.*; import java.util.*; /* For SAX XML parsing */ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; /** * Handle the parsing of an XML file and the generation of a Comments object. * * All HTML written for the comments sections in the report must * use tags such as <p/> rather than just <p>, since the XML * parser used requires that or matching end elements. * * From http://www.w3.org/TR/2000/REC-xhtml1-20000126: * "Empty elements must either have an end tag or the start tag must end with /<". * * See the file LICENSE.txt for copyright details. * @author Matthew Doar, mdoar@pobox.com */ class CommentsHandler extends DefaultHandler { /** The Comments object which is populated from the XML file. */ public Comments comments_ = null; /** The current SingleComment object being populated. */ private List currSingleComment_ = null; // SingleComment[] /** Set if in text. */ private boolean inText = false; /** The current text which is being assembled from chunks. */ private String currentText = null; /** The stack of SingleComments still waiting for comment text. */ private LinkedList tagStack = null; /** Default constructor. */ public CommentsHandler(Comments comments) { comments_ = comments; tagStack = new LinkedList(); } public void startDocument() { } public void endDocument() { if (trace) comments_.dump(); } public void startElement(java.lang.String uri, java.lang.String localName, java.lang.String qName, Attributes attributes) { // The change to JAXP compliance produced this change. if (localName.equals("")) localName = qName; if (localName.compareTo("comments") == 0) { String commentsName = attributes.getValue("name"); String version = attributes.getValue("jdversion"); // Not used yet if (commentsName == null) { System.out.println("Error: no identifier found in the comments XML file."); System.exit(3); } // Check the given names against the names of the APIs int idx1 = JDiff.oldFileName.lastIndexOf('.'); int idx2 = JDiff.newFileName.lastIndexOf('.'); String filename2 = JDiff.oldFileName.substring(0, idx1) + "_to_" + JDiff.newFileName.substring(0, idx2); if (filename2.compareTo(commentsName) != 0) { System.out.println("Warning: API identifier in the comments XML file (" + filename2 + ") differs from the name of the file."); } } else if (localName.compareTo("comment") == 0) { currSingleComment_ = new ArrayList(); // SingleComment[]; } else if (localName.compareTo("identifier") == 0) { // May have multiple identifiers for one comment's text String id = attributes.getValue("id"); SingleComment newComment = new SingleComment(id, null); // Store it here until we can add text to it currSingleComment_.add(newComment); } else if (localName.compareTo("text") == 0) { inText = true; currentText = null; } else { if (inText) { // Start of an element, probably an HTML element addStartTagToText(localName, attributes); } else { System.out.println("Error: unknown element type: " + localName); System.exit(-1); } } } public void endElement(java.lang.String uri, java.lang.String localName, java.lang.String qName) { if (localName.equals("")) localName = qName; if (localName.compareTo("text") == 0) { inText = false; addTextToComments(); } else if (inText) { addEndTagToText(localName); } } /** Deal with a chunk of text. The text may come in multiple chunks. */ public void characters(char[] ch, int start, int length) { if (inText) { String chunk = new String(ch, start, length); if (currentText == null) currentText = chunk; else currentText += chunk; } } /** * Trim the current text, check it is a sentence and add it to all * the comments which are waiting for it. */ public void addTextToComments() { // Eliminate any whitespace at each end of the text. currentText = currentText.trim(); // Check that it is a sentence if (!currentText.endsWith(".") && !currentText.endsWith("?") && !currentText.endsWith("!") && currentText.compareTo(Comments.placeHolderText) != 0) { System.out.println("Warning: text of comment does not end in a period: " + currentText); } // Add this comment to all the SingleComments waiting for it Iterator iter = currSingleComment_.iterator(); while (iter.hasNext()) { SingleComment currComment = (SingleComment)(iter.next()); if (currComment.text_ == null) currComment.text_ = currentText; else currComment.text_ += currentText; comments_.addComment(currComment); } } /** * Add the start tag to the current comment text. */ public void addStartTagToText(String localName, Attributes attributes) { // Need to insert the HTML tag into the current text String currentHTMLTag = localName; // Save the tag in a stack tagStack.add(currentHTMLTag); String tag = "<" + currentHTMLTag; // Now add all the attributes into the current text int len = attributes.getLength(); for (int i = 0; i < len; i++) { String name = attributes.getLocalName(i); String value = attributes.getValue(i); tag += " " + name + "=\"" + value+ "\""; } // End the tag if (Comments.isMinimizedTag(currentHTMLTag)) { tag += "/>"; } else { tag += ">"; } // Now insert the HTML tag into the current text if (currentText == null) currentText = tag; else currentText += tag; } /** * Add the end tag to the current comment text. */ public void addEndTagToText(String localName) { // Close the current HTML tag String currentHTMLTag = (String)(tagStack.removeLast()); if (!Comments.isMinimizedTag(currentHTMLTag)) currentText += "</" + currentHTMLTag + ">"; } public void warning(SAXParseException e) { System.out.println("Warning (" + e.getLineNumber() + "): parsing XML comments file:" + e); e.printStackTrace(); } public void error(SAXParseException e) { System.out.println("Error (" + e.getLineNumber() + "): parsing XML comments file:" + e); e.printStackTrace(); System.exit(1); } public void fatalError(SAXParseException e) { System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML comments file:" + e); e.printStackTrace(); System.exit(1); } /** Set to enable increased logging verbosity for debugging. */ private static final boolean trace = false; }