@@ -50,20 +50,16 @@ class Filter < HTML::Pipeline::Filter
5050 (?=\s ) # followed by whitespace
5151 /x
5252
53- ListSelector = [
54- # select UL/OL
55- ".//li[starts-with(text(),'[ ]')]/.." ,
56- ".//li[starts-with(text(),'[x]')]/.." ,
57- # and those wrapped in Ps
58- ".//li/p[1][starts-with(text(),'[ ]')]/../.." ,
59- ".//li/p[1][starts-with(text(),'[x]')]/../.."
60- ] . join ( ' | ' ) . freeze
61-
62- # Selects all LIs from a TaskList UL/OL
63- ItemSelector = ".//li" . freeze
53+ ListItemSelector = ".//li[task_list_item(.)]" . freeze
54+
55+ class XPathSelectorFunction
56+ def self . task_list_item ( nodes )
57+ nodes if nodes . text =~ ItemPattern
58+ end
59+ end
6460
6561 # Selects first P tag of an LI, if present
66- ItemParaSelector = ".// p[1]" . freeze
62+ ItemParaSelector = "./p[1]" . freeze
6763
6864 # List of `TaskList::Item` objects that were recognized in the document.
6965 # This is available in the result hash as `:task_list_items`.
@@ -103,20 +99,22 @@ def render_task_list_item(item)
10399 #
104100 # Returns an Array of Nokogiri::XML::Element objects for ordered and
105101 # unordered lists.
106- def task_lists
107- doc . xpath ( ListSelector )
102+ def list_items
103+ doc . xpath ( ListItemSelector , XPathSelectorFunction )
108104 end
109105
110- # Public: filters a Nokogiri::XML::Element ordered/unordered list, marking
111- # up the list items in order to add behavior and include metadata.
106+ # Filters the source for task list items.
112107 #
113- # Modifies the provided node.
108+ # Each item is wrapped in HTML to identify, style, and layer
109+ # useful behavior on top of.
110+ #
111+ # Modifications apply to the parsed document directly.
114112 #
115113 # Returns nothing.
116- def filter_list ( node )
117- add_css_class ( node , 'task-list' )
114+ def filter!
115+ list_items . reverse . each do |li |
116+ add_css_class ( li . parent , 'task-list' )
118117
119- node . xpath ( ItemSelector ) . each do |li |
120118 outer , inner =
121119 if p = li . xpath ( ItemParaSelector ) [ 0 ]
122120 [ p , p . inner_html ]
@@ -125,28 +123,15 @@ def filter_list(node)
125123 end
126124 if match = ( inner . chomp =~ ItemPattern && $1)
127125 item = TaskList ::Item . new ( match , inner )
128- task_list_items << item
126+ # prepend because we're iterating in reverse
127+ task_list_items . unshift item
129128
130129 add_css_class ( li , 'task-list-item' )
131130 outer . inner_html = render_task_list_item ( item )
132131 end
133132 end
134133 end
135134
136- # Filters the source for task list items.
137- #
138- # Each item is wrapped in HTML to identify, style, and layer
139- # useful behavior on top of.
140- #
141- # Modifications apply to the parsed document directly.
142- #
143- # Returns nothing.
144- def filter!
145- task_lists . each do |node |
146- filter_list node
147- end
148- end
149-
150135 def call
151136 filter!
152137 doc
@@ -156,6 +141,7 @@ def call
156141 # names.
157142 def add_css_class ( node , *new_class_names )
158143 class_names = ( node [ 'class' ] || '' ) . split ( ' ' )
144+ return if new_class_names . all? { |klass | class_names . include? ( klass ) }
159145 class_names . concat ( new_class_names )
160146 node [ 'class' ] = class_names . uniq . join ( ' ' )
161147 end
0 commit comments