1package jdiff;
2
3import java.io.*;
4import java.util.*;
5
6/* For SAX XML parsing */
7import org.xml.sax.Attributes;
8import org.xml.sax.SAXException;
9import org.xml.sax.SAXParseException;
10import org.xml.sax.XMLReader;
11import org.xml.sax.helpers.DefaultHandler;
12
13/**
14 * Handle the parsing of an XML file and the generation of a Comments object.
15 *
16 * All HTML written for the comments sections in the report must
17 * use tags such as <p/> rather than just <p>, since the XML
18 * parser used requires that or matching end elements.
19 *
20 * From http://www.w3.org/TR/2000/REC-xhtml1-20000126:
21 * "Empty elements must either have an end tag or the start tag must end with /<".
22 *
23 * See the file LICENSE.txt for copyright details.
24 * @author Matthew Doar, mdoar@pobox.com
25 */
26class CommentsHandler extends DefaultHandler {
27
28    /** The Comments object which is populated from the XML file. */
29    public Comments comments_ = null;
30
31    /** The current SingleComment object being populated. */
32    private List currSingleComment_ = null; // SingleComment[]
33
34    /** Set if in text. */
35    private boolean inText = false;
36
37    /** The current text which is being assembled from chunks. */
38    private String currentText = null;
39
40    /** The stack of SingleComments still waiting for comment text. */
41    private LinkedList tagStack = null;
42
43    /** Default constructor. */
44    public CommentsHandler(Comments comments) {
45        comments_ = comments;
46        tagStack = new LinkedList();
47    }
48
49    public void startDocument() {
50    }
51
52    public void endDocument() {
53        if (trace)
54            comments_.dump();
55    }
56
57    public void startElement(java.lang.String uri, java.lang.String localName,
58                             java.lang.String qName, Attributes attributes) {
59	// The change to JAXP compliance produced this change.
60	if (localName.equals(""))
61	    localName = qName;
62        if (localName.compareTo("comments") == 0) {
63            String commentsName = attributes.getValue("name");
64            String version = attributes.getValue("jdversion"); // Not used yet
65            if (commentsName == null) {
66                System.out.println("Error: no identifier found in the comments XML file.");
67                System.exit(3);
68            }
69            // Check the given names against the names of the APIs
70            int idx1 = JDiff.oldFileName.lastIndexOf('.');
71            int idx2 = JDiff.newFileName.lastIndexOf('.');
72            String filename2 = JDiff.oldFileName.substring(0, idx1) +
73                "_to_" + JDiff.newFileName.substring(0, idx2);
74            if (filename2.compareTo(commentsName) != 0) {
75                System.out.println("Warning: API identifier in the comments XML file (" + filename2 + ") differs from the name of the file.");
76            }
77        } else if (localName.compareTo("comment") == 0) {
78            currSingleComment_ = new ArrayList(); // SingleComment[];
79        } else if (localName.compareTo("identifier") == 0) {
80            // May have multiple identifiers for one comment's text
81            String id = attributes.getValue("id");
82            SingleComment newComment = new SingleComment(id, null);
83            // Store it here until we can add text to it
84            currSingleComment_.add(newComment);
85        } else if (localName.compareTo("text") == 0) {
86            inText = true;
87            currentText = null;
88        } else {
89            if (inText) {
90                // Start of an element, probably an HTML element
91                addStartTagToText(localName, attributes);
92            } else {
93                System.out.println("Error: unknown element type: " + localName);
94                System.exit(-1);
95            }
96        }
97    }
98
99    public void endElement(java.lang.String uri, java.lang.String localName,
100                           java.lang.String qName) {
101	if (localName.equals(""))
102	    localName = qName;
103        if (localName.compareTo("text") == 0) {
104            inText = false;
105            addTextToComments();
106        } else if (inText) {
107            addEndTagToText(localName);
108        }
109
110    }
111
112    /** Deal with a chunk of text. The text may come in multiple chunks. */
113    public void characters(char[] ch, int start, int length) {
114        if (inText) {
115            String chunk = new String(ch, start, length);
116            if (currentText == null)
117                currentText = chunk;
118            else
119                currentText += chunk;
120        }
121    }
122
123    /**
124     * Trim the current text, check it is a sentence and add it to all
125     * the comments which are waiting for it.
126     */
127    public void addTextToComments() {
128        // Eliminate any whitespace at each end of the text.
129        currentText = currentText.trim();
130        // Check that it is a sentence
131        if (!currentText.endsWith(".") &&
132            !currentText.endsWith("?") &&
133            !currentText.endsWith("!") &&
134            currentText.compareTo(Comments.placeHolderText) != 0) {
135            System.out.println("Warning: text of comment does not end in a period: " + currentText);
136        }
137        // Add this comment to all the SingleComments waiting for it
138        Iterator iter = currSingleComment_.iterator();
139        while (iter.hasNext()) {
140            SingleComment currComment = (SingleComment)(iter.next());
141            if (currComment.text_ == null)
142                currComment.text_ = currentText;
143            else
144                currComment.text_ += currentText;
145            comments_.addComment(currComment);
146        }
147    }
148
149    /**
150     * Add the start tag to the current comment text.
151     */
152    public void addStartTagToText(String localName, Attributes attributes) {
153        // Need to insert the HTML tag into the current text
154        String currentHTMLTag = localName;
155        // Save the tag in a stack
156        tagStack.add(currentHTMLTag);
157        String tag = "<" + currentHTMLTag;
158        // Now add all the attributes into the current text
159        int len = attributes.getLength();
160        for (int i = 0; i < len; i++) {
161            String name = attributes.getLocalName(i);
162            String value = attributes.getValue(i);
163            tag += " " + name + "=\"" + value+ "\"";
164        }
165
166        // End the tag
167        if (Comments.isMinimizedTag(currentHTMLTag)) {
168            tag += "/>";
169        } else {
170            tag += ">";
171        }
172        // Now insert the HTML tag into the current text
173        if (currentText == null)
174            currentText = tag;
175        else
176            currentText += tag;
177    }
178
179    /**
180     * Add the end tag to the current comment text.
181     */
182    public void addEndTagToText(String localName) {
183        // Close the current HTML tag
184        String currentHTMLTag = (String)(tagStack.removeLast());
185        if (!Comments.isMinimizedTag(currentHTMLTag))
186            currentText += "</" + currentHTMLTag + ">";
187    }
188
189    public void warning(SAXParseException e) {
190        System.out.println("Warning (" + e.getLineNumber() + "): parsing XML comments file:" + e);
191        e.printStackTrace();
192    }
193
194    public void error(SAXParseException e) {
195        System.out.println("Error (" + e.getLineNumber() + "): parsing XML comments file:" + e);
196        e.printStackTrace();
197        System.exit(1);
198    }
199
200    public void fatalError(SAXParseException e) {
201        System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML comments file:" + e);
202        e.printStackTrace();
203        System.exit(1);
204    }
205
206    /** Set to enable increased logging verbosity for debugging. */
207    private static final boolean trace = false;
208
209}
210
211