1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43:
44: import ;
45: import ;
46: import ;
47: import ;
48:
49: import ;
50: import ;
51: import ;
52:
53:
64: public abstract class htmlValidator
65: {
66:
70: protected class hTag
71: {
72: protected final Element element;
73: protected final HTML.Tag tag;
74: protected final TagElement tgElement;
75: protected boolean forcibly_closed;
76: protected node validationTrace;
77:
78: protected hTag(TagElement an_element)
79: {
80: element = an_element.getElement();
81: tag = an_element.getHTMLTag();
82: tgElement = an_element;
83:
84: if (element.content != null)
85: validationTrace = transformer.transform(element.content, dtd);
86: }
87:
88:
97: protected void forciblyCloseDueContext()
98: {
99: forcibly_closed = true;
100: }
101:
102:
107: protected void forciblyCloseDueEndOfStream()
108: {
109: forcibly_closed = true;
110: handleSupposedEndTag(element);
111: }
112: }
113:
114:
117: protected final DTD dtd;
118:
119:
122: protected final LinkedList stack = new LinkedList();
123:
124:
129: public htmlValidator(DTD a_dtd)
130: {
131: dtd = a_dtd;
132: }
133:
134:
137: public void closeAll()
138: {
139: hTag h;
140: while (!stack.isEmpty())
141: {
142: h = (hTag) stack.getLast();
143: if (!h.forcibly_closed && !h.element.omitEnd())
144: s_error("Unclosed <" + h.tag + ">, closing at the end of stream");
145:
146: handleSupposedEndTag(h.element);
147:
148: closeTag(h.tgElement);
149: }
150: }
151:
152:
156: public void closeTag(TagElement tElement)
157: {
158: HTML.Tag tag = tElement.getHTMLTag();
159: hTag x;
160: hTag close;
161:
162: if (!stack.isEmpty())
163: {
164: ListIterator iter = stack.listIterator(stack.size());
165:
166: while (iter.hasPrevious())
167: {
168: x = (hTag) iter.previous();
169: if (tag.equals(x.tag))
170: {
171: if (x.forcibly_closed && !x.element.omitEnd())
172: s_error("The tag <" + x.tag +
173: "> has already been forcibly closed"
174: );
175:
176:
177:
178:
179: closing:
180: if (x.element.content != null)
181: {
182: iter = stack.listIterator(stack.size());
183: while (iter.hasPrevious())
184: {
185: close = (hTag) iter.previous();
186: if (close == x)
187: break closing;
188: handleSupposedEndTag(close.element);
189: iter.remove();
190: }
191: }
192:
193: stack.remove(x);
194: return;
195: }
196: }
197: }
198: s_error("Closing unopened <" + tag + ">");
199: }
200:
201:
207: public void openTag(TagElement tElement, htmlAttributeSet parameters)
208: {
209:
210:
211: if (tElement.fictional())
212: return;
213:
214: validateParameters(tElement, parameters);
215:
216:
217: if (stack.isEmpty() && tElement.getHTMLTag() != HTML.Tag.HTML)
218: {
219: Element html = dtd.getElement(HTML.Tag.HTML.toString());
220: openFictionalTag(html);
221: }
222:
223: Object v = tagIsValidForContext(tElement);
224: if (v != Boolean.TRUE)
225: {
226:
227:
228: if (v instanceof Element)
229: {
230: int n = 0;
231: while (v instanceof Element && (n++ < 100))
232: {
233: Element fe = (Element) v;
234:
235:
236: getCurrentContentModel().show(fe);
237: openFictionalTag(fe);
238:
239: Object vv = tagIsValidForContext(tElement);
240: if (vv instanceof Element)
241: {
242: openFictionalTag((Element) vv);
243:
244: Object vx = tagIsValidForContext(tElement);
245: if (vx instanceof Element)
246: openFictionalTag((Element) vx);
247: }
248: else if (vv == Boolean.FALSE)
249: {
250:
251:
252: if (fe.omitEnd())
253: {
254:
255: closeLast();
256: vv = tagIsValidForContext(tElement);
257: if (vv instanceof Element)
258:
259:
260: openFictionalTag((Element) vv);
261: }
262: }
263: v = tagIsValidForContext(tElement);
264: }
265: }
266: else
267: {
268: if (!stack.isEmpty())
269: {
270: closing:
271: do
272: {
273: hTag last = (hTag) stack.getLast();
274: if (last.element.omitEnd())
275: {
276: closeLast();
277: v = tagIsValidForContext(tElement);
278: if (v instanceof Element)
279: {
280: openFictionalTag((Element) v);
281: break closing;
282: }
283: }
284: else
285: break closing;
286: }
287: while (v == Boolean.FALSE && !stack.isEmpty());
288: }
289: }
290: }
291:
292: stack.add(new hTag(tElement));
293: }
294:
295:
298: public void restart()
299: {
300: stack.clear();
301: }
302:
303:
314: public Object tagIsValidForContext(TagElement tElement)
315: {
316:
317: node cv = getCurrentContentModel();
318:
319: if (cv != null)
320: return cv.show(tElement.getElement());
321:
322:
323: ListIterator iter = stack.listIterator(stack.size());
324: hTag t;
325: final int idx = tElement.getElement().index;
326:
327:
328: if (idx >= 0)
329: {
330: BitSet inclusions = new BitSet();
331: while (iter.hasPrevious())
332: {
333: t = (hTag) iter.previous();
334: if (!t.forcibly_closed)
335: {
336: if (t.element.exclusions != null &&
337: t.element.exclusions.get(idx)
338: )
339: return Boolean.FALSE;
340:
341: if (t.element.inclusions != null)
342: inclusions.or(t.element.inclusions);
343: }
344: }
345: if (!inclusions.get(idx))
346: return Boolean.FALSE;
347: }
348: return Boolean.TRUE;
349: }
350:
351:
356: public void validateTag(TagElement tElement, htmlAttributeSet parameters)
357: {
358: openTag(tElement, parameters);
359: closeTag(tElement);
360: }
361:
362:
366: protected void checkContentModel(TagElement tElement, boolean first)
367: {
368: if (stack.isEmpty())
369: return;
370:
371: hTag last = (hTag) stack.getLast();
372: if (last.validationTrace == null)
373: return;
374:
375: Object r = last.validationTrace.show(tElement.getElement());
376: if (r == Boolean.FALSE)
377: s_error("The <" + last.element + "> does not match the content model " +
378: last.validationTrace
379: );
380: else if (r instanceof Element)
381: {
382: if (!first)
383: closeTag(last.tgElement);
384: handleSupposedStartTag((Element) r);
385: openTag(new TagElement((Element) r), null);
386: }
387: }
388:
389:
400: protected abstract void handleSupposedEndTag(Element element);
401:
402:
409: protected abstract void handleSupposedStartTag(Element element);
410:
411:
416: protected abstract void s_error(String msg);
417:
418:
426: protected void validateParameters(TagElement tag, htmlAttributeSet parameters)
427: {
428: if (parameters == null ||
429: parameters == htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET ||
430: parameters == SimpleAttributeSet.EMPTY
431: )
432: return;
433:
434: Enumeration enumeration = parameters.getAttributeNames();
435:
436: while (enumeration.hasMoreElements())
437: {
438: validateAttribute(tag, parameters, enumeration);
439: }
440:
441:
442: AttributeList a = tag.getElement().getAttributes();
443:
444: while (a != null)
445: {
446: if (a.getModifier() == DTDConstants.REQUIRED)
447: if (parameters.getAttribute(a.getName()) == null)
448: {
449: s_error("Missing required attribute '" + a.getName() + "' for <" +
450: tag.getHTMLTag() + ">"
451: );
452: }
453: a = a.next;
454: }
455: }
456:
457: private node getCurrentContentModel()
458: {
459: if (!stack.isEmpty())
460: {
461: hTag last = (hTag) stack.getLast();
462: return last.validationTrace;
463: }
464: else
465: return null;
466: }
467:
468: private void closeLast()
469: {
470: handleSupposedEndTag(((hTag) stack.getLast()).element);
471: stack.removeLast();
472: }
473:
474: private void openFictionalTag(Element e)
475: {
476: handleSupposedStartTag(e);
477: stack.add(new hTag(new TagElement(e, true)));
478: if (!e.omitStart())
479: s_error("<" + e + "> is expected (supposing it)");
480: }
481:
482: private void validateAttribute(TagElement tag, htmlAttributeSet parameters,
483: Enumeration enumeration
484: )
485: {
486: Object foundAttribute;
487: AttributeList dtdAttribute;
488: foundAttribute = enumeration.nextElement();
489: dtdAttribute = tag.getElement().getAttribute(foundAttribute.toString());
490: if (dtdAttribute == null)
491: {
492: StringBuffer valid =
493: new StringBuffer("The tag <" + tag.getHTMLTag() +
494: "> cannot contain the attribute '" + foundAttribute +
495: "'. The valid attributes for this tag are: "
496: );
497:
498: AttributeList a = tag.getElement().getAttributes();
499:
500: while (a != null)
501: {
502: valid.append(a.name.toUpperCase());
503: valid.append(' ');
504: a = a.next;
505: }
506: s_error(valid.toString());
507: }
508:
509: else
510: {
511: String value = parameters.getAttribute(foundAttribute).toString();
512:
513: if (dtdAttribute.type == DTDConstants.NUMBER)
514: validateNumberAttribute(tag, foundAttribute, value);
515:
516: if (dtdAttribute.type == DTDConstants.NAME ||
517: dtdAttribute.type == DTDConstants.ID
518: )
519: validateNameOrIdAttribute(tag, foundAttribute, value);
520:
521: if (dtdAttribute.values != null)
522: validateAttributeWithValueList(tag, foundAttribute, dtdAttribute,
523: value
524: );
525: }
526: }
527:
528: private void validateAttributeWithValueList(TagElement tag,
529: Object foundAttribute,
530: AttributeList dtdAttribute,
531: String value
532: )
533: {
534: if (!dtdAttribute.values.contains(value.toLowerCase()) &&
535: !dtdAttribute.values.contains(value.toUpperCase())
536: )
537: {
538: StringBuffer valid;
539: if (dtdAttribute.values.size() == 1)
540: valid =
541: new StringBuffer("The attribute '" + foundAttribute +
542: "' of the tag <" + tag.getHTMLTag() +
543: "> cannot have the value '" + value +
544: "'. The only valid value is "
545: );
546: else
547: valid =
548: new StringBuffer("The attribute '" + foundAttribute +
549: "' of the tag <" + tag.getHTMLTag() +
550: "> cannot have the value '" + value + "'. The " +
551: dtdAttribute.values.size() +
552: " valid values are: "
553: );
554:
555: Enumeration vv = dtdAttribute.values.elements();
556: while (vv.hasMoreElements())
557: {
558: valid.append('"');
559: valid.append(vv.nextElement());
560: valid.append("\" ");
561: }
562: s_error(valid.toString());
563: }
564: }
565:
566: private void validateNameOrIdAttribute(TagElement tag, Object foundAttribute,
567: String value
568: )
569: {
570: boolean ok = true;
571:
572: if (!Character.isLetter(value.charAt(0)))
573: ok = false;
574:
575: char c;
576: for (int i = 0; i < value.length(); i++)
577: {
578: c = value.charAt(i);
579: if (!(
580: Character.isLetter(c) || Character.isDigit(c) ||
581: "".indexOf(c) >= 0
582: )
583: )
584: ok = false;
585: }
586: if (!ok)
587: s_error("The '" + foundAttribute + "' attribute of the tag <" +
588: tag.getHTMLTag() + "> must start from letter and consist of " +
589: "letters, digits, hypens, colons, underscores and periods. " +
590: "It cannot be '" + value + "'"
591: );
592: }
593:
594: private void validateNumberAttribute(TagElement tag, Object foundAttribute,
595: String value
596: )
597: {
598: try
599: {
600: Integer.parseInt(value);
601: }
602: catch (NumberFormatException ex)
603: {
604: s_error("The '" + foundAttribute + "' attribute of the tag <" +
605: tag.getHTMLTag() + "> must be a valid number and not '" +
606: value + "'"
607: );
608: }
609: }
610: }