Skip to main content
summaryrefslogtreecommitdiffstats
blob: cc7327e90a6b82ade264e8972f38da0d5b190472 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
/*******************************************************************************
 * Copyright (c) 2003 - 2005 University Of British Columbia and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University Of British Columbia - initial API and implementation
 *******************************************************************************/
package org.eclipse.mylar.bugzilla.core.internal;

import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;

import javax.security.auth.login.LoginException;

import org.eclipse.mylar.bugzilla.core.Attribute;
import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer.Token;
import org.eclipse.mylar.bugzilla.ui.wizard.NewBugModel;


/**
 * @author Shawn Minto
 *
 * This class parses the valid attribute values for a new bug
 */
public class NewBugParser {
    /** Tokenizer used on the stream */
    private HtmlStreamTokenizer tokenizer;
	
	/** Flag for whether we need to try to get the product or not */
	private static boolean getProd = false;
 	
	public NewBugParser(Reader in) {
		tokenizer = new HtmlStreamTokenizer(in, null);
	}
	
	/**
	 * Parse the new bugs valid attributes
	 * @param nbm A reference to a NewBugModel where all of the information is stored
	 * @throws IOException
	 * @throws ParseException
	 * @throws LoginException
	 */
	public void parseBugAttributes(NewBugModel nbm, boolean retrieveProducts) throws IOException, ParseException, LoginException
	{
		nbm.attributes.clear();   // clear any attriubtes in bug model from a previous product
		
		NewBugParser.getProd = retrieveProducts;
		
		// create a new bug report and set the parser state to the start state
		ParserState state = ParserState.START;
		String attribute = null;

		boolean isTitle = false;
		boolean possibleBadLogin = false;
		boolean isErrorState = false;
		String title = "";
		// Default error message
		String errorMsg = "Bugzilla could not get the needed bug attribute since your login name or password is incorrect.  Please check your settings in the bugzilla preferences.";
		
		for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) {
			// make sure that bugzilla doesn't want us to login
			if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.TITLE && !((HtmlTag)(token.getValue())).isEndTag()) {
				isTitle = true;
				continue;
			}
			
			if(isTitle) {
				// get all of the data in the title tag to compare with
				if(token.getType() != Token.TAG) {
					title += ((StringBuffer)token.getValue()).toString().toLowerCase() + " ";
					continue;
				}
				else if(token.getType() == Token.TAG && ((HtmlTag)token.getValue()).getTagType() == HtmlTag.Type.TITLE && ((HtmlTag)token.getValue()).isEndTag()) {
					// check if the title looks like we may have a problem with login
					if(title.indexOf("login") != -1) {
						possibleBadLogin = true;		 	// generic / default msg passed to constructor re: bad login 
					}
					if((title.indexOf("invalid") != -1 && title.indexOf("password") != -1) ||  title.indexOf("check e-mail") != -1 || title.indexOf("error") != -1) {					
						possibleBadLogin = true;
						isErrorState = true;			    // set flag so appropriate msg is provide for the exception
						errorMsg = "";						// error message will be parsed from error page
					}

					isTitle = false;
					title = "";
				}
				continue;
			}
			
			
			// we have found the start of an attribute name
			if ((state == ParserState.ATT_NAME || state == ParserState.START) && token.getType() == Token.TAG ) {
				HtmlTag tag = (HtmlTag) token.getValue();
				if (tag.getTagType() == HtmlTag.Type.TD && "right".equalsIgnoreCase(tag.getAttribute("align"))) {
					// parse the attribute's name
					attribute = parseAttributeName();
					if(attribute == null) continue;
					state = ParserState.ATT_VALUE;
					continue;
				}
				
				if (tag.getTagType() == HtmlTag.Type.TD && "#ff0000".equalsIgnoreCase(tag.getAttribute("bgcolor"))) {					
					state = ParserState.ERROR;
					continue;
				}
			}

			// we have found the start of attribute values
			if (state == ParserState.ATT_VALUE && token.getType() == Token.TAG) {
				HtmlTag tag = (HtmlTag) token.getValue();
				if (tag.getTagType() == HtmlTag.Type.TD) {
					// parse the attribute values
					parseAttributeValue(nbm, attribute);
					
					state = ParserState.ATT_NAME;
					attribute = null;
					continue;
				}
			}
			// page being parsed contains an Error message
			// parse error message so it can be given to the constructor of the exception
			// so an appropriate error message is displayed
			if(state == ParserState.ERROR && isErrorState){	
				// tag should be text token, not a tag
				// get the error message
				if(token.getType() == Token.TEXT) {
					// get string value of next token to add to error messgage
					// unescape the string so any escape sequences parsed appear unescaped in the details pane
					errorMsg +=  HtmlStreamTokenizer.unescape(  ((StringBuffer)token.getValue()).toString()   )  + " ";
				}
				// expect </font> tag to indicate end of error end msg
				// set next state to continue parsing remainder of page
				else if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.FONT && ((HtmlTag)(token.getValue())).isEndTag()) {
					state = ParserState.ATT_NAME;
				}
				continue;
			}
			
			if((state == ParserState.ATT_NAME || state == ParserState.START) && token.getType() == Token.TAG) {
				HtmlTag tag = (HtmlTag)token.getValue();
				if(tag.getTagType() == HtmlTag.Type.INPUT && tag.getAttribute("type") != null &&"hidden".equalsIgnoreCase(tag.getAttribute("type").trim())) {
					Attribute a = new Attribute(tag.getAttribute("name"));
					a.setParameterName(tag.getAttribute("name"));
					a.setValue(tag.getAttribute("value"));
					a.setHidden(true);
					nbm.attributes.put(a.getName(), a);
					continue;
				}
			}
		}
		
		// if we have no attributes and we suspect a bad login, we assume that the login info was bad
		if(possibleBadLogin && (nbm.getAttributes() == null || nbm.getAttributes().size() == 0)) {
			throw new LoginException(errorMsg);
		}
	}

	/**
	 * Parse the case where we have found an attribute name
	 * @param tokenizer The tokenizer to use to find the name
	 * @return The name of the attribute
	 * @throws IOException
	 * @throws ParseException
	 */
	private String parseAttributeName()
		throws IOException, ParseException {
		StringBuffer sb = new StringBuffer();

		parseTableCell(sb);
		HtmlStreamTokenizer.unescape(sb);
		// remove the colon if there is one
		if(sb.length() == 0)
			return null;
		if (sb.charAt(sb.length() - 1) == ':') {
			sb.deleteCharAt(sb.length() - 1);
		}
		return sb.toString();
	}
	
	/**
	 * Reads text into a StringBuffer until it encounters a close table cell tag (&lt;/TD&gt;) or start of another cell.
	 * The text is appended to the existing value of the buffer. <b>NOTE:</b> Does not handle nested cells!
	 * @param tokenizer
	 * @param sb
	 * @throws IOException
	 * @throws ParseException
	 */
	private void parseTableCell(StringBuffer sb)
		throws IOException, ParseException {
		boolean noWhitespace = false;
		for (HtmlStreamTokenizer.Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) {
			if (token.getType() == Token.TAG) {
				HtmlTag tag = (HtmlTag) token.getValue();
				if (tag.getTagType() == HtmlTag.Type.TD) {
					if (!tag.isEndTag()) {
						tokenizer.pushback(token);
					}
					break;
				}
				noWhitespace = token.getWhitespace().length() == 0;
			}
			else if (token.getType() == Token.TEXT) {
				// if there was no whitespace between the tag and the
				// preceding text, don't insert whitespace before this text
				// unless it is there in the source 
				if (!noWhitespace && token.getWhitespace().length() > 0 && sb.length() > 0) {
					sb.append(' ');
				}
				sb.append((StringBuffer)token.getValue());
			}
		}
	}
	
	/**
	 * Parse the case where we have found attribute values
	 * @param nbm The NewBugModel that is to contain information about a new bug
	 * @param attributeName The name of the attribute that we are parsing
	 * @param tokenizer The tokenizer to use for parsing
	 * @throws IOException
	 * @throws ParseException
	 */
	private void parseAttributeValue(
		NewBugModel nbm,
		String attributeName)
		throws IOException, ParseException {

		HtmlStreamTokenizer.Token token = tokenizer.nextToken();
		if (token.getType() == Token.TAG) {
			HtmlTag tag = (HtmlTag) token.getValue();
			if (tag.getTagType() == HtmlTag.Type.SELECT && !tag.isEndTag()) {
				String parameterName = tag.getAttribute("name");
				parseSelect(nbm, attributeName, parameterName);
			}
			else if (tag.getTagType() == HtmlTag.Type.INPUT && !tag.isEndTag()) {
				parseInput(nbm, attributeName, tag);
			}
			else if (!tag.isEndTag()) {
				parseAttributeValueCell(nbm, attributeName);
			}
		}
		else {
			StringBuffer sb = new StringBuffer();
			if (token.getType() == Token.TEXT) {
				sb.append((StringBuffer) token.getValue());
				parseAttributeValueCell(nbm, attributeName, sb);
			}
		}
	}
	
	/**
	 * Parse the case where the attribute value is just text in a table cell
	 * @param attributeName The name of the attribute we are parsing
	 * @param tokenizer The tokenizer to use for parsing
	 * @throws IOException
	 * @throws ParseException
	 */
	private void parseAttributeValueCell(NewBugModel nbm, String attributeName)
			throws IOException, ParseException {
		StringBuffer sb = new StringBuffer();

		parseAttributeValueCell(nbm, attributeName, sb);
	}

	private void parseAttributeValueCell(
		NewBugModel nbm,
		String attributeName,
		StringBuffer sb)
		throws IOException, ParseException {
	
		parseTableCell(sb);
		HtmlStreamTokenizer.unescape(sb);
		
		// if we need the product we will get it
		if(getProd && attributeName.equalsIgnoreCase("product")) {
			nbm.setProduct(sb.toString());
		}
	}
	
	/**
	 * Parse the case where the attribute value is an input
	 * @param nbm The new bug model to add information that we get to
	 * @param attributeName The name of the attribute that we are parsing
	 * @param tag The HTML tag that we are currently on
	 * @throws IOException
	 */
	private static void parseInput(
		NewBugModel nbm,
		String attributeName,
		HtmlTag tag)
		throws IOException {

		Attribute a = new Attribute(attributeName);
		a.setParameterName(tag.getAttribute("name"));
		String value = tag.getAttribute("value");
		if (value == null) value = "";

		// if we found the summary, add it to the bug report
		if (attributeName.equalsIgnoreCase("summary")) {
			nbm.setSummary(value);
		}
		else if (attributeName.equalsIgnoreCase("Attachments")) {
			// do nothing - not a problem after 2.14
		}
		else if (attributeName.equalsIgnoreCase("add cc")) {
			// do nothing
		}
		else if (attributeName.toLowerCase().startsWith("cc")) {
			// do nothing cc's are options not inputs
		}
		else {
			// otherwise just add the attribute
			a.setValue(value);
			nbm.attributes.put(attributeName, a);
		}
	}
	
	/**
	 * Parse the case where the attribute value is an option
	 * @param nbm The NewBugModel that we are storing information in
	 * @param attributeName The name of the attribute that we are parsing
	 * @param parameterName The SELECT tag's name
	 * @param tokenizer The tokenizer that we are using for parsing
	 * @throws IOException
	 * @throws ParseException
	 */
	private void parseSelect(
		NewBugModel nbm,
		String attributeName,
		String parameterName)
		throws IOException, ParseException {
	
		boolean first = false;
		Attribute a = new Attribute(attributeName);
		a.setParameterName(parameterName);
	
		HtmlStreamTokenizer.Token token = tokenizer.nextToken();
		while ( token.getType() != Token.EOF) {
			if (token.getType() == Token.TAG) {
				HtmlTag tag = (HtmlTag) token.getValue();
				if (tag.getTagType() == HtmlTag.Type.SELECT && tag.isEndTag()) break;
				if (tag.getTagType() == HtmlTag.Type.OPTION && !tag.isEndTag()) {
					String optionName = tag.getAttribute("value");
					boolean selected = tag.hasAttribute("selected");
					StringBuffer optionText = new StringBuffer();
					for (token = tokenizer.nextToken(); token.getType() == Token.TEXT; token = tokenizer.nextToken()) {
						if (optionText.length() > 0) {
							optionText.append(' ');
						}
						optionText.append((StringBuffer) token.getValue());
					}
					a.addOptionValue(optionText.toString(), optionName);

					if (selected || first) {
						a.setValue(optionText.toString());
						first = false;
					}
				}
				else {
					token = tokenizer.nextToken();
				}
			}
			else {
				token = tokenizer.nextToken();
			}
		}

		if(!(nbm.attributes).containsKey(attributeName)) {
			(nbm.attributes).put(attributeName, a);
		}
	}
	
	/**
	 * Enum class for describing current state of Bugzilla report parser.
	 */
	private static class ParserState 
	{
		/** An instance of the start state */
		protected static final ParserState START = new ParserState("start");

		/** An instance of the state when the parser found an attribute name */
		protected static final ParserState ATT_NAME = new ParserState("att_name");

		/** An instance of the state when the parser found an attribute value */
		protected static final ParserState ATT_VALUE = new ParserState("att_value");
		/** An instance of the state when an error page is found */
		protected static final ParserState ERROR = new ParserState("error");
		/** State's human-readable name */
		private String name;

		/**
		 * Constructor
		 * @param description - The states human readable name
		 */
		private ParserState(String description)
		{
			this.name = description;
		}

		@Override
		public String toString()
		{
			return name;
		}
	}
}

Back to the top