193 |
194 |
194 // <editor-fold defaultstate="collapsed" desc="Text and entities."> |
195 // <editor-fold defaultstate="collapsed" desc="Text and entities."> |
195 |
196 |
196 @Override |
197 @Override |
197 public Void visitText(TextTree tree, Void ignore) { |
198 public Void visitText(TextTree tree, Void ignore) { |
198 if (!tree.getBody().trim().isEmpty()) { |
199 if (hasNonWhitespace(tree)) { |
|
200 checkAllowsText(tree); |
199 markEnclosingTag(Flag.HAS_TEXT); |
201 markEnclosingTag(Flag.HAS_TEXT); |
200 } |
202 } |
201 return null; |
203 return null; |
202 } |
204 } |
203 |
205 |
204 @Override |
206 @Override |
205 public Void visitEntity(EntityTree tree, Void ignore) { |
207 public Void visitEntity(EntityTree tree, Void ignore) { |
|
208 checkAllowsText(tree); |
206 markEnclosingTag(Flag.HAS_TEXT); |
209 markEnclosingTag(Flag.HAS_TEXT); |
207 String name = tree.getName().toString(); |
210 String name = tree.getName().toString(); |
208 if (name.startsWith("#")) { |
211 if (name.startsWith("#")) { |
209 int v = name.toLowerCase().startsWith("#x") |
212 int v = name.toLowerCase().startsWith("#x") |
210 ? Integer.parseInt(name.substring(2), 16) |
213 ? Integer.parseInt(name.substring(2), 16) |
216 env.messages.error(HTML, tree, "dc.entity.invalid", name); |
219 env.messages.error(HTML, tree, "dc.entity.invalid", name); |
217 } |
220 } |
218 return null; |
221 return null; |
219 } |
222 } |
220 |
223 |
|
224 void checkAllowsText(DocTree tree) { |
|
225 TagStackItem top = tagStack.peek(); |
|
226 if (top != null |
|
227 && top.tree.getKind() == DocTree.Kind.START_ELEMENT |
|
228 && !top.tag.acceptsText()) { |
|
229 if (top.flags.add(Flag.REPORTED_BAD_INLINE)) { |
|
230 env.messages.error(HTML, tree, "dc.text.not.allowed", |
|
231 ((StartElementTree) top.tree).getName()); |
|
232 } |
|
233 } |
|
234 } |
|
235 |
221 // </editor-fold> |
236 // </editor-fold> |
222 |
237 |
223 // <editor-fold defaultstate="collapsed" desc="HTML elements"> |
238 // <editor-fold defaultstate="collapsed" desc="HTML elements"> |
224 |
239 |
225 @Override |
240 @Override |
228 final Name treeName = tree.getName(); |
243 final Name treeName = tree.getName(); |
229 final HtmlTag t = HtmlTag.get(treeName); |
244 final HtmlTag t = HtmlTag.get(treeName); |
230 if (t == null) { |
245 if (t == null) { |
231 env.messages.error(HTML, tree, "dc.tag.unknown", treeName); |
246 env.messages.error(HTML, tree, "dc.tag.unknown", treeName); |
232 } else { |
247 } else { |
|
248 for (TagStackItem tsi: tagStack) { |
|
249 if (tsi.tag.accepts(t)) { |
|
250 while (tagStack.peek() != tsi) tagStack.pop(); |
|
251 break; |
|
252 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) |
|
253 break; |
|
254 } |
|
255 |
|
256 checkStructure(tree, t); |
|
257 |
233 // tag specific checks |
258 // tag specific checks |
234 switch (t) { |
259 switch (t) { |
235 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3> |
260 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3> |
236 case H1: case H2: case H3: case H4: case H5: case H6: |
261 case H1: case H2: case H3: case H4: case H5: case H6: |
237 checkHeader(tree, t); |
262 checkHeader(tree, t); |
238 break; |
263 break; |
239 // <p> inside <pre> |
|
240 case P: |
|
241 TagStackItem top = tagStack.peek(); |
|
242 if (top != null && top.tag == HtmlTag.PRE) |
|
243 env.messages.warning(HTML, tree, "dc.tag.p.in.pre"); |
|
244 break; |
|
245 } |
|
246 |
|
247 // check that only block tags and inline tags are used, |
|
248 // and that blocks tags are not used within inline tags |
|
249 switch (t.blockType) { |
|
250 case INLINE: |
|
251 break; |
|
252 case BLOCK: |
|
253 TagStackItem top = tagStack.peek(); |
|
254 if (top != null && top.tag != null && top.tag.blockType == HtmlTag.BlockType.INLINE) { |
|
255 switch (top.tree.getKind()) { |
|
256 case START_ELEMENT: { |
|
257 Name name = ((StartElementTree) top.tree).getName(); |
|
258 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element", |
|
259 treeName, name); |
|
260 break; |
|
261 } |
|
262 case LINK: |
|
263 case LINK_PLAIN: { |
|
264 String name = top.tree.getKind().tagName; |
|
265 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag", |
|
266 treeName, name); |
|
267 break; |
|
268 } |
|
269 default: |
|
270 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.other", |
|
271 treeName); |
|
272 } |
|
273 } |
|
274 break; |
|
275 case OTHER: |
|
276 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName); |
|
277 break; |
|
278 default: |
|
279 throw new AssertionError(); |
|
280 } |
264 } |
281 |
265 |
282 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) { |
266 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) { |
283 for (TagStackItem i: tagStack) { |
267 for (TagStackItem i: tagStack) { |
284 if (t == i.tag) { |
268 if (t == i.tag) { |
320 } finally { |
304 } finally { |
321 |
305 |
322 if (t == null || t.endKind == HtmlTag.EndKind.NONE) |
306 if (t == null || t.endKind == HtmlTag.EndKind.NONE) |
323 tagStack.pop(); |
307 tagStack.pop(); |
324 } |
308 } |
|
309 } |
|
310 |
|
311 private void checkStructure(StartElementTree tree, HtmlTag t) { |
|
312 Name treeName = tree.getName(); |
|
313 TagStackItem top = tagStack.peek(); |
|
314 switch (t.blockType) { |
|
315 case BLOCK: |
|
316 if (top == null || top.tag.accepts(t)) |
|
317 return; |
|
318 |
|
319 switch (top.tree.getKind()) { |
|
320 case START_ELEMENT: { |
|
321 if (top.tag.blockType == HtmlTag.BlockType.INLINE) { |
|
322 Name name = ((StartElementTree) top.tree).getName(); |
|
323 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element", |
|
324 treeName, name); |
|
325 return; |
|
326 } |
|
327 } |
|
328 break; |
|
329 |
|
330 case LINK: |
|
331 case LINK_PLAIN: { |
|
332 String name = top.tree.getKind().tagName; |
|
333 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag", |
|
334 treeName, name); |
|
335 return; |
|
336 } |
|
337 } |
|
338 break; |
|
339 |
|
340 case INLINE: |
|
341 if (top == null || top.tag.accepts(t)) |
|
342 return; |
|
343 break; |
|
344 |
|
345 case LIST_ITEM: |
|
346 case TABLE_ITEM: |
|
347 if (top != null) { |
|
348 // reset this flag so subsequent bad inline content gets reported |
|
349 top.flags.remove(Flag.REPORTED_BAD_INLINE); |
|
350 if (top.tag.accepts(t)) |
|
351 return; |
|
352 } |
|
353 break; |
|
354 |
|
355 case OTHER: |
|
356 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName); |
|
357 return; |
|
358 } |
|
359 |
|
360 env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName); |
325 } |
361 } |
326 |
362 |
327 private void checkHeader(StartElementTree tree, HtmlTag tag) { |
363 private void checkHeader(StartElementTree tree, HtmlTag tag) { |
328 // verify the new tag |
364 // verify the new tag |
329 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) { |
365 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) { |
376 if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT) |
412 if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT) |
377 && !top.flags.contains(Flag.HAS_TEXT) |
413 && !top.flags.contains(Flag.HAS_TEXT) |
378 && !top.flags.contains(Flag.HAS_ELEMENT)) { |
414 && !top.flags.contains(Flag.HAS_ELEMENT)) { |
379 env.messages.warning(HTML, tree, "dc.tag.empty", treeName); |
415 env.messages.warning(HTML, tree, "dc.tag.empty", treeName); |
380 } |
416 } |
381 if (t.flags.contains(HtmlTag.Flag.NO_TEXT) |
|
382 && top.flags.contains(Flag.HAS_TEXT)) { |
|
383 env.messages.error(HTML, tree, "dc.text.not.allowed", treeName); |
|
384 } |
|
385 tagStack.pop(); |
417 tagStack.pop(); |
386 done = true; |
418 done = true; |
387 break; |
419 break; |
388 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) { |
420 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) { |
389 tagStack.pop(); |
421 tagStack.pop(); |
761 |
793 |
762 void warnIfEmpty(DocTree tree, List<? extends DocTree> list) { |
794 void warnIfEmpty(DocTree tree, List<? extends DocTree> list) { |
763 for (DocTree d: list) { |
795 for (DocTree d: list) { |
764 switch (d.getKind()) { |
796 switch (d.getKind()) { |
765 case TEXT: |
797 case TEXT: |
766 if (!((TextTree) d).getBody().trim().isEmpty()) |
798 if (hasNonWhitespace((TextTree) d)) |
767 return; |
799 return; |
768 break; |
800 break; |
769 default: |
801 default: |
770 return; |
802 return; |
771 } |
803 } |
772 } |
804 } |
773 env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName); |
805 env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName); |
774 } |
806 } |
|
807 |
|
808 boolean hasNonWhitespace(TextTree tree) { |
|
809 String s = tree.getBody(); |
|
810 for (int i = 0; i < s.length(); i++) { |
|
811 if (!Character.isWhitespace(s.charAt(i))) |
|
812 return true; |
|
813 } |
|
814 return false; |
|
815 } |
|
816 |
775 // </editor-fold> |
817 // </editor-fold> |
776 |
818 |
777 } |
819 } |