Skip to content
Snippets Groups Projects
Commit 862b8834 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Automatically close unclosed HTML tags.

Fixes an old problem with some unclosed <b> or <div> messing up
the layout of docs.

In addition to automatically closing the tags, a warning gets printed.
parent f020e2a7
Branches
Tags
No related merge requests found
require 'rubygems'
require 'strscan'
require 'rdiscount'
require 'jsduck/logger'
require 'jsduck/inline/link'
require 'jsduck/inline/img'
require 'jsduck/inline/video'
......@@ -19,6 +20,7 @@ module JsDuck
@inline_img = Inline::Img.new(opts)
@inline_video = Inline::Video.new(opts)
@inline_example = Inline::Example.new(opts)
@doc_context = {}
end
# Sets base path to prefix images from {@img} tags.
......@@ -41,13 +43,14 @@ module JsDuck
# Sets up instance to work in context of particular doc object.
# Used for error reporting.
def doc_context=(doc)
@doc_context = doc
@inline_video.doc_context = doc
@inline_link.doc_context = doc
end
# Returns the current documentation context
def doc_context
@inline_link.doc_context
@doc_context
end
# JsDuck::Relations for looking up class names.
......@@ -94,10 +97,10 @@ module JsDuck
s = StringScanner.new(input)
out = ""
# Keep track of the nesting level of <a> tags. We're not
# auto-detecting class names when inside <a>. Normally links
# shouldn't be nested, but just to be extra safe.
open_a_tags = 0
# Keep track of open HTML tags. We're not auto-detecting class
# names when inside <a>. Also we want to close down the unclosed
# tags.
open_tags = []
while !s.eos? do
if substitute = @inline_link.replace(s)
......@@ -111,28 +114,63 @@ module JsDuck
out += s.scan(/[{]/)
elsif substitute = @inline_example.replace(s)
out += substitute
elsif s.check(/<a\b/)
# Increment number of open <a> tags.
open_a_tags += 1
elsif s.check(/<\w/)
# Push the tag to open_tags stack
out += s.scan(/</)
tag = s.scan(/\w+/)
open_tags.unshift(tag) unless VOID_TAGS[tag]
out += tag
out += s.scan_until(/>|\Z/)
elsif s.check(/<\/a>/)
# <a> closed, auto-detection may continue when no more <a> tags open.
open_a_tags -= 1
out += s.scan(/<\/a>/)
elsif s.check(/<\/\w+>/)
# Close the tag
out += s.scan(/<\//)
tag = s.scan(/\w+/)
if open_tags.include?(tag)
# delete the most recent unclosed tag in our tags stack
open_tags.delete_at(open_tags.index(tag))
end
out += tag
out += s.scan(/>/)
elsif s.check(/</)
# Ignore all other HTML tags
out += s.scan_until(/>|\Z/)
# Ignore plain '<' char.
out += s.scan(/</)
else
# Replace class names in the following text up to next "<" or "{"
# but only when we're not inside <a>...</a>
text = s.scan(/[^{<]+/)
out += open_a_tags > 0 ? text : @inline_link.create_magic_links(text)
out += open_tags.include?("a") ? text : @inline_link.create_magic_links(text)
end
end
if open_tags.length > 0
# Print warnings for unclosed tags
ctx = @doc_context
tag_list = open_tags.map {|tag| "<#{tag}>" }.join(", ")
Logger.warn(:html, "Unclosed HTML tag: #{tag_list}", ctx[:filename], ctx[:linenr])
# Automatically close all unclosed tags
out += open_tags.map {|tag| "</#{tag}>" }.join
end
out
end
# Tags that don't require closing
VOID_TAGS = {
"base" => true,
"link" => true,
"meta" => true,
"hr" => true,
"br" => true,
"wbr" => true,
"area" => true,
"img" => true,
"param" => true,
"input" => true,
"isindex" => true,
"option" => true,
}
# Creates a link based on the link template.
def link(cls, member, anchor_text, type=nil, static=nil)
@inline_link.link(cls, member, anchor_text, type, static)
......
......@@ -20,6 +20,7 @@ module JsDuck
[:link, "{@link} to unknown class or member"],
[:link_ambiguous, "{@link} is ambiguous"],
[:link_auto, "Auto-detected link to unknown class or member"],
[:html, "Unclosed HTML tag."],
[:alt_name, "Name used as both classname and alternate classname"],
[:name_missing, "Member or parameter has no name"],
......
......@@ -367,6 +367,18 @@ describe JsDuck::DocFormatter do
@formatter.format("Hello **world**").should =~ /Hello <strong>world<\/strong>/
end
it "closes unclosed <b> tags" do
@formatter.format("<b>Hello").should =~ /<b>Hello.*<\/b>/m
end
it "closes unclosed <a> and <b> in correct order" do
@formatter.format("<a><b>Hello").should =~ /<\/b><\/a>/
end
it "doesn't close unclosed <img> tags" do
@formatter.format("<img>").should_not =~ /<\/img>/
end
shared_examples_for "code blocks" do
it "contains text before" do
@html.should =~ /Some code/
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment