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: node ccm = getCurrentContentModel();
237: if (ccm != null)
238: ccm.show(fe);
239: openFictionalTag(fe);
240:
241: Object vv = tagIsValidForContext(tElement);
242: if (vv instanceof Element)
243: {
244: openFictionalTag((Element) vv);
245:
246: Object vx = tagIsValidForContext(tElement);
247: if (vx instanceof Element)
248: openFictionalTag((Element) vx);
249: }
250: else if (vv == Boolean.FALSE)
251: {
252:
253:
254: if (fe.omitEnd())
255: {
256:
257: closeLast();
258: vv = tagIsValidForContext(tElement);
259: if (vv instanceof Element)
260:
261:
262: openFictionalTag((Element) vv);
263: }
264: }
265: v = tagIsValidForContext(tElement);
266: }
267: }
268: else
269: {
270: if (!stack.isEmpty())
271: {
272: closing:
273: do
274: {
275: hTag last = (hTag) stack.getLast();
276: if (last.element.omitEnd())
277: {
278: closeLast();
279: v = tagIsValidForContext(tElement);
280: if (v instanceof Element)
281: {
282: openFictionalTag((Element) v);
283: break closing;
284: }
285: }
286: else
287: break closing;
288: }
289: while (v == Boolean.FALSE && !stack.isEmpty());
290: }
291: }
292: }
293:
294: stack.add(new hTag(tElement));
295: }
296:
297:
300: public void restart()
301: {
302: stack.clear();
303: }
304:
305:
315: public Object tagIsValidForContext(TagElement tElement)
316: {
317:
318: node cv = getCurrentContentModel();
319:
320: if (cv != null)
321: return cv.show(tElement.getElement());
322:
323:
324: ListIterator iter = stack.listIterator(stack.size());
325: hTag t = null;
326: final int idx = tElement.getElement().index;
327:
328:
329: if (idx >= 0)
330: {
331: BitSet inclusions = new BitSet();
332: while (iter.hasPrevious())
333: {
334: t = (hTag) iter.previous();
335: if (! t.forcibly_closed)
336: {
337: if (t.element.exclusions != null
338: && t.element.exclusions.get(idx))
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: {
347:
348:
349:
350: Element P = dtd.getElement(HTML_401F.P);
351: if (inclusions.get(P.index))
352: return P;
353: else
354: return Boolean.FALSE;
355: }
356: }
357: return Boolean.TRUE;
358: }
359:
360:
365: public void validateTag(TagElement tElement, htmlAttributeSet parameters)
366: {
367: openTag(tElement, parameters);
368: closeTag(tElement);
369: }
370:
371:
375: protected void checkContentModel(TagElement tElement, boolean first)
376: {
377: if (stack.isEmpty())
378: return;
379:
380: hTag last = (hTag) stack.getLast();
381: if (last.validationTrace == null)
382: return;
383:
384: Object r = last.validationTrace.show(tElement.getElement());
385: if (r == Boolean.FALSE)
386: s_error("The <" + last.element + "> does not match the content model " +
387: last.validationTrace
388: );
389: else if (r instanceof Element)
390: {
391: if (!first)
392: closeTag(last.tgElement);
393: handleSupposedStartTag((Element) r);
394: openTag(new TagElement((Element) r), null);
395: }
396: }
397:
398:
409: protected abstract void handleSupposedEndTag(Element element);
410:
411:
418: protected abstract void handleSupposedStartTag(Element element);
419:
420:
425: protected abstract void s_error(String msg);
426:
427:
435: protected void validateParameters(TagElement tag, htmlAttributeSet parameters)
436: {
437: if (parameters == null ||
438: parameters == htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET ||
439: parameters == SimpleAttributeSet.EMPTY
440: )
441: return;
442:
443: Enumeration enumeration = parameters.getAttributeNames();
444:
445: while (enumeration.hasMoreElements())
446: {
447: validateAttribute(tag, parameters, enumeration);
448: }
449:
450:
451: AttributeList a = tag.getElement().getAttributes();
452:
453: while (a != null)
454: {
455: if (a.getModifier() == DTDConstants.REQUIRED)
456: if (parameters.getAttribute(a.getName()) == null)
457: {
458: s_error("Missing required attribute '" + a.getName() + "' for <" +
459: tag.getHTMLTag() + ">"
460: );
461: }
462: a = a.next;
463: }
464: }
465:
466: private node getCurrentContentModel()
467: {
468: if (!stack.isEmpty())
469: {
470: hTag last = (hTag) stack.getLast();
471: return last.validationTrace;
472: }
473: else
474: return null;
475: }
476:
477: private void closeLast()
478: {
479: handleSupposedEndTag(((hTag) stack.getLast()).element);
480: stack.removeLast();
481: }
482:
483: private void openFictionalTag(Element e)
484: {
485: handleSupposedStartTag(e);
486: stack.add(new hTag(new TagElement(e, true)));
487: if (!e.omitStart())
488: s_error("<" + e + "> is expected (supposing it)");
489: }
490:
491: private void validateAttribute(TagElement tag, htmlAttributeSet parameters,
492: Enumeration enumeration
493: )
494: {
495: Object foundAttribute;
496: AttributeList dtdAttribute;
497: foundAttribute = enumeration.nextElement();
498: dtdAttribute = tag.getElement().getAttribute(foundAttribute.toString());
499: if (dtdAttribute == null)
500: {
501: StringBuffer valid =
502: new StringBuffer("The tag <" + tag.getHTMLTag() +
503: "> cannot contain the attribute '" + foundAttribute +
504: "'. The valid attributes for this tag are: "
505: );
506:
507: AttributeList a = tag.getElement().getAttributes();
508:
509: while (a != null)
510: {
511: valid.append(a.name.toUpperCase());
512: valid.append(' ');
513: a = a.next;
514: }
515: s_error(valid.toString());
516: }
517:
518: else
519: {
520: String value = parameters.getAttribute(foundAttribute).toString();
521:
522: if (dtdAttribute.type == DTDConstants.NUMBER)
523: validateNumberAttribute(tag, foundAttribute, value);
524:
525: if (dtdAttribute.type == DTDConstants.NAME ||
526: dtdAttribute.type == DTDConstants.ID
527: )
528: validateNameOrIdAttribute(tag, foundAttribute, value);
529:
530: if (dtdAttribute.values != null)
531: validateAttributeWithValueList(tag, foundAttribute, dtdAttribute,
532: value
533: );
534: }
535: }
536:
537: private void validateAttributeWithValueList(TagElement tag,
538: Object foundAttribute,
539: AttributeList dtdAttribute,
540: String value
541: )
542: {
543: if (!dtdAttribute.values.contains(value.toLowerCase()) &&
544: !dtdAttribute.values.contains(value.toUpperCase())
545: )
546: {
547: StringBuffer valid;
548: if (dtdAttribute.values.size() == 1)
549: valid =
550: new StringBuffer("The attribute '" + foundAttribute +
551: "' of the tag <" + tag.getHTMLTag() +
552: "> cannot have the value '" + value +
553: "'. The only valid value is "
554: );
555: else
556: valid =
557: new StringBuffer("The attribute '" + foundAttribute +
558: "' of the tag <" + tag.getHTMLTag() +
559: "> cannot have the value '" + value + "'. The " +
560: dtdAttribute.values.size() +
561: " valid values are: "
562: );
563:
564: Enumeration vv = dtdAttribute.values.elements();
565: while (vv.hasMoreElements())
566: {
567: valid.append('"');
568: valid.append(vv.nextElement());
569: valid.append("\" ");
570: }
571: s_error(valid.toString());
572: }
573: }
574:
575: private void validateNameOrIdAttribute(TagElement tag, Object foundAttribute,
576: String value
577: )
578: {
579: boolean ok = true;
580:
581: if (!Character.isLetter(value.charAt(0)))
582: ok = false;
583:
584: char c;
585: for (int i = 0; i < value.length(); i++)
586: {
587: c = value.charAt(i);
588: if (!(
589: Character.isLetter(c) || Character.isDigit(c) ||
590: "".indexOf(c) >= 0
591: )
592: )
593: ok = false;
594: }
595: if (!ok)
596: s_error("The '" + foundAttribute + "' attribute of the tag <" +
597: tag.getHTMLTag() + "> must start from letter and consist of " +
598: "letters, digits, hypens, colons, underscores and periods. " +
599: "It cannot be '" + value + "'"
600: );
601: }
602:
603: private void validateNumberAttribute(TagElement tag, Object foundAttribute,
604: String value
605: )
606: {
607: try
608: {
609: Integer.parseInt(value);
610: }
611: catch (NumberFormatException ex)
612: {
613: s_error("The '" + foundAttribute + "' attribute of the tag <" +
614: tag.getHTMLTag() + "> must be a valid number and not '" +
615: value + "'"
616: );
617: }
618: }
619: }