Skip to content

Commit

Permalink
Fixed the w3c validation bug!
Browse files Browse the repository at this point in the history
Unfortuantely, this breaks text output.  I'll have to fix that sometime soon,
but it will be a good opportunity to implement text wrapping anyway.

This was annoyingly difficult.  There are two main changes: First, a new
subclass of ListBuilder, ItemBuilder is now used to wrap the inner content of
lists--the contained phrases in most cases.  This list builder determines when
other ListBuilders can be appended to the content, since sub-lists should be
nested under items.

Second, all of the HTML output now goes through ListBuilder and to_html is no
longer called on the parsed SyntaxNodes.  This is because ListBuilder needed to
add item tags (e.g., <li>) to each level in addition to the list type tags
(<ul>).  Instead of adding parameters to ListBuilder to make this work,
ListBuilder is now subclassed for each list type and those subclasses take care
of knowing how to render the correct HTML tags.  This will probably work out
well when I go back to reimplement the text formatting.
  • Loading branch information
rdblue committed Nov 13, 2010
1 parent e2b94d9 commit 913d6c8
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 52 deletions.
125 changes: 78 additions & 47 deletions lib/marker/lists.rb
Expand Up @@ -9,54 +9,49 @@
module Marker #:nodoc:
# used to collect list lines into lists structured hierarchically, like in HTML
class ListBuilder
attr_reader :tag, :contents, :attrs
attr_reader :contents

def initialize( contents, tag, attrs = {} )
@contents = [contents].flatten
@tag = tag
@attrs = attrs
def initialize( contents = nil )
@contents = ( contents ? [contents] : [] )
end

# returns true if the l has the same tag
def ===( l )
( l.is_a? self.class and tag == l.tag )
# returns true if the other has the same type
def ===( other )
( other.is_a? self.class )
end

def <<( l )
# append a new child to contents. if that child can be combined with the
# last child already known, then we add them together.
def <<( other )
last = contents.last

if last and last === l
last += l
if last and last === other
last += other
else
contents << l
contents << other
end

self
end

def +( l )
l.contents.each { |i| self << i } if self === l
def +( other )
other.contents.each { |item| self << item } if self === other

self
end

def to_html( options = {} )
if tag
"<#{tag}" +
( attrs.any?? ' ' : '' ) +
attrs.map { |k, v|
"#{k}='#{v}'"
}.join(' ') +
">" +
contents.map { |i|
i.to_html(options)
}.join +
"</#{tag}>"
else
contents.map { |i|
i.to_html(options)
}.join
end
contents_to_html( options )
end

def contents_to_html( options = {} )
contents.map { |item|
item_to_html( item, options )
}.join
end

def item_to_html( item, options = {} )
item.to_html( options )
end

def to_s( options = {} )
Expand All @@ -71,29 +66,46 @@ def to_s( options = {} )
end
end

# a ListBuilder to encapsulate raw list item contents
class ItemBuilder < ListBuilder
def ===( other )
other.is_a? ListBuilder and not other.is_a? ItemBuilder
end

def +( other )
self << other
end
end

class List < RecursiveList
def to_html( options = {} )
l = ListBuilder.new( [], nil )
l = ListBuilder.new
to_a.each do |item|
l << item.structure
end
l.to_html(options)
end

def to_s( options = {} )
l = ListBuilder.new( [], nil )
l = ListBuilder.new
to_a.each do |item|
l << item.structure
end
l.to_s(options)
end
end

class Bulleted < ParseNode
class BulletedListBuilder < ListBuilder
def to_html( options = {} )
"<li>#{phrase.to_html(options)}</li>"
"<ul>#{contents_to_html(options)}</ul>"
end

def item_to_html( item, options = {} )
"<li>#{item.to_html(options)}</li>"
end
end

class Bulleted < ParseNode
def to_s( options = {} )
indent = (options[:indent] || 0)
' ' * (indent > 0 ? indent - 1 : 0) +
Expand All @@ -102,9 +114,9 @@ def to_s( options = {} )

def structure
if phrase
ListBuilder.new( self, :ul )
BulletedListBuilder.new( ItemBuilder.new( phrase ) )
else
ListBuilder.new( list_item.structure, :ul )
BulletedListBuilder.new( list_item.structure )
end
end

Expand All @@ -114,11 +126,17 @@ def phrase
end
end

class Numbered < ParseNode
class NumberedListBuilder < ListBuilder
def to_html( options = {} )
"<li>#{phrase.to_html(options)}</li>"
"<ol>#{contents_to_html(options)}</ol>"
end

def item_to_html( item, options = {} )
"<li>#{item.to_html(options)}</li>"
end
end

class Numbered < ParseNode
def to_s( options = {} )
indent = (options[:indent] || 0)
' ' * (indent > 0 ? indent - 1 : 0) +
Expand All @@ -127,9 +145,9 @@ def to_s( options = {} )

def structure
if phrase
ListBuilder.new( self, :ol )
NumberedListBuilder.new( ItemBuilder.new( phrase ) )
else
ListBuilder.new( list_item.structure, :ol )
NumberedListBuilder.new( list_item.structure )
end
end

Expand All @@ -139,11 +157,17 @@ def phrase
end
end

class Indented < ParseNode
class IndentedListBuilder < ListBuilder
def to_html( options = {} )
"<div>#{phrase.to_html(options)}</div>"
"<div class='indent'>#{contents_to_html(options)}</div>"
end

def item_to_html( item, options = {} )
"<div class='indent_item'>#{item.to_html(options)}</div>"
end
end

class Indented < ParseNode
def to_s( options = {} )
indent = (options[:indent] || 0)
' ' * (indent > 0 ? indent : 0) +
Expand All @@ -152,9 +176,9 @@ def to_s( options = {} )

def structure
if phrase
ListBuilder.new( self, :div, :class => 'indent' )
IndentedListBuilder.new( ItemBuilder.new( phrase ) )
else
ListBuilder.new( list_item.structure, :div, :class => 'indent' )
IndentedListBuilder.new( list_item.structure )
end
end

Expand All @@ -164,20 +188,27 @@ def phrase
end
end

class Definition < ParseNode
class DefinitionListBuilder < ListBuilder
def to_html( options = {} )
"<dl>#{contents_to_html(options)}</dl>"
end

def item_to_html( item, options = {} )
term, definition = item
"<dt>#{term.to_html(options)}</dt>" +
( definition ? "<dd>#{definition.to_html(options)}</dd>" : "" )
"<dd>#{definition.to_html(options)}</dd>"
end
end

class Definition < ParseNode
def to_s( options = {} )
indent = (options[:indent] || 0)
' ' * (indent > 0 ? indent - 1: 0) +
"#{term.to_s(options)} :: #{definition.to_s(options)}"
end

def structure
ListBuilder.new( self, :dl )
DefinitionListBuilder.new( [term, ItemBuilder.new( definition )] )
end

#-- defaults ++
Expand Down
17 changes: 12 additions & 5 deletions test/lists_test.rb
Expand Up @@ -14,7 +14,7 @@ def test_nested_bulleted_list
text = "* List item 1\n** List item 2\n* List item 3"
markup = Marker.parse text

assert_match("<ul><li>List item 1</li><ul><li>List item 2</li></ul><li>List item 3</li></ul>", markup.to_html)
assert_match("<ul><li>List item 1<ul><li>List item 2</li></ul></li><li>List item 3</li></ul>", markup.to_html)
end

def test_numbered_list
Expand All @@ -28,7 +28,7 @@ def test_nested_numbered_list
text = "# List item 1\n## List item 2\n# List item 3"
markup = Marker.parse text

assert_match("<ol><li>List item 1</li><ol><li>List item 2</li></ol><li>List item 3</li></ol>", markup.to_html)
assert_match("<ol><li>List item 1<ol><li>List item 2</li></ol></li><li>List item 3</li></ol>", markup.to_html)
end

def test_definition_list
Expand Down Expand Up @@ -59,15 +59,22 @@ def test_mixed_list
markup = Marker.parse text

assert_match("<ol><li>List item 1</li></ol><ul><li>List item 2</li></ul><ol><li>List item 3</li></ol><dl><dt>List item 4</dt>" +
"<dd>definition</dd></dl><div class='indent'><div>List item 5</div></div><ul><li>List item 6</li></ul>", markup.to_html)
"<dd>definition</dd></dl><div class='indent'><div class='indent_item'>List item 5</div></div><ul><li>List item 6</li></ul>", markup.to_html)
end

def test_nested_mixed_list
text = "# List item 1\n#* List item 2\n# List item 3\n## List item 4\n#; List item 5 : definition\n#:List item 6"
markup = Marker.parse text

assert_match("<ol><li>List item 1</li><ul><li>List item 2</li></ul><li>List item 3</li><ol><li>List item 4</li></ol>" +
"<dl><dt>List item 5</dt><dd>definition</dd></dl><div class='indent'><div>List item 6</div></div></ol>", markup.to_html)
assert_match("<ol><li>List item 1<ul><li>List item 2</li></ul></li><li>List item 3<ol><li>List item 4</li></ol>" +
"<dl><dt>List item 5</dt><dd>definition</dd></dl><div class='indent'><div class='indent_item'>List item 6</div></div></li></ol>", markup.to_html)
end

def test_bare_nested_list
text = "*** item"
markup = Marker.parse text

assert_match("<ul><li><ul><li><ul><li>item</li></ul></li></ul></li></ul>", markup.to_html)
end

end

0 comments on commit 913d6c8

Please sign in to comment.