article_json 0.3.8 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/README.md +127 -79
  4. data/bin/article_json_export_amp.rb +1 -0
  5. data/bin/{article_json_export_facebook.rb → article_json_export_apple_news.rb} +4 -4
  6. data/bin/article_json_export_html.rb +1 -0
  7. data/bin/article_json_export_plain_text.rb +1 -0
  8. data/bin/article_json_parse_google_doc.rb +1 -0
  9. data/bin/check_google_doc_export.rb +41 -0
  10. data/bin/update_oembed_request-stubs.sh +1 -3
  11. data/bin/update_reference_document.sh +3 -3
  12. data/lib/article_json/article.rb +17 -9
  13. data/lib/article_json/configuration.rb +6 -5
  14. data/lib/article_json/elements/base.rb +0 -1
  15. data/lib/article_json/elements/heading.rb +0 -1
  16. data/lib/article_json/elements/image.rb +0 -1
  17. data/lib/article_json/elements/list.rb +0 -1
  18. data/lib/article_json/elements/paragraph.rb +1 -1
  19. data/lib/article_json/elements/quote.rb +0 -1
  20. data/lib/article_json/elements/text.rb +1 -1
  21. data/lib/article_json/elements/text_box.rb +0 -1
  22. data/lib/article_json/export/amp/custom_element_library_resolver.rb +0 -1
  23. data/lib/article_json/export/amp/elements/embed.rb +43 -31
  24. data/lib/article_json/export/amp/elements/image.rb +7 -5
  25. data/lib/article_json/export/amp/exporter.rb +4 -2
  26. data/lib/article_json/export/apple_news/elements/base.rb +53 -0
  27. data/lib/article_json/export/apple_news/elements/embed.rb +130 -0
  28. data/lib/article_json/export/apple_news/elements/heading.rb +32 -0
  29. data/lib/article_json/export/apple_news/elements/image.rb +59 -0
  30. data/lib/article_json/export/apple_news/elements/list.rb +67 -0
  31. data/lib/article_json/export/apple_news/elements/paragraph.rb +36 -0
  32. data/lib/article_json/export/apple_news/elements/quote.rb +60 -0
  33. data/lib/article_json/export/apple_news/elements/text.rb +55 -0
  34. data/lib/article_json/export/apple_news/elements/text_box.rb +51 -0
  35. data/lib/article_json/export/apple_news/exporter.rb +37 -0
  36. data/lib/article_json/export/common/html/elements/embed.rb +2 -1
  37. data/lib/article_json/export/common/html/elements/image.rb +2 -1
  38. data/lib/article_json/export/common/html/elements/text.rb +2 -0
  39. data/lib/article_json/import/google_doc/html/embedded_parser.rb +1 -0
  40. data/lib/article_json/import/google_doc/html/heading_parser.rb +5 -5
  41. data/lib/article_json/import/google_doc/html/image_parser.rb +18 -2
  42. data/lib/article_json/import/google_doc/html/list_parser.rb +2 -2
  43. data/lib/article_json/import/google_doc/html/node_analyzer.rb +25 -3
  44. data/lib/article_json/import/google_doc/html/parser.rb +7 -1
  45. data/lib/article_json/import/google_doc/html/shared/caption.rb +1 -0
  46. data/lib/article_json/import/google_doc/html/shared/float.rb +2 -0
  47. data/lib/article_json/import/google_doc/html/text_box_parser.rb +2 -1
  48. data/lib/article_json/import/google_doc/html/text_parser.rb +2 -0
  49. data/lib/article_json/utils/additional_element_placer.rb +2 -0
  50. data/lib/article_json/utils/o_embed_resolver/base.rb +14 -4
  51. data/lib/article_json/utils/o_embed_resolver/facebook_video.rb +17 -1
  52. data/lib/article_json/utils/o_embed_resolver/slideshare.rb +2 -2
  53. data/lib/article_json/utils/o_embed_resolver/youtube_video.rb +14 -1
  54. data/lib/article_json/version.rb +1 -1
  55. data/lib/article_json.rb +11 -11
  56. metadata +29 -26
  57. data/lib/article_json/export/facebook_instant_article/elements/base.rb +0 -30
  58. data/lib/article_json/export/facebook_instant_article/elements/embed.rb +0 -44
  59. data/lib/article_json/export/facebook_instant_article/elements/heading.rb +0 -11
  60. data/lib/article_json/export/facebook_instant_article/elements/image.rb +0 -11
  61. data/lib/article_json/export/facebook_instant_article/elements/list.rb +0 -11
  62. data/lib/article_json/export/facebook_instant_article/elements/paragraph.rb +0 -11
  63. data/lib/article_json/export/facebook_instant_article/elements/quote.rb +0 -30
  64. data/lib/article_json/export/facebook_instant_article/elements/text.rb +0 -11
  65. data/lib/article_json/export/facebook_instant_article/elements/text_box.rb +0 -40
  66. data/lib/article_json/export/facebook_instant_article/exporter.rb +0 -17
@@ -14,7 +14,7 @@ module ArticleJSON
14
14
  def elements
15
15
  @elements ||= begin
16
16
  if @additional_elements.any?
17
- ArticleJSON::Utils::AdditionalElementPlacer
17
+ @additional_element_placer_class
18
18
  .new(@article_elements, @additional_elements)
19
19
  .merge_elements
20
20
  else
@@ -62,16 +62,16 @@ module ArticleJSON
62
62
  amp_exporter.html
63
63
  end
64
64
 
65
- # Exporter instance for FacebookInstantArticle
66
- # @return [ArticleJSON::Export::FacebookInstantArticle::Exporter]
67
- def facebook_instant_article_exporter
68
- ArticleJSON::Export::FacebookInstantArticle::Exporter.new(elements)
65
+ # Exporter instance for AppleNews
66
+ # @return [ArticleJSON::Export::AppleNews::Exporter]
67
+ def apple_news_exporter
68
+ ArticleJSON::Export::AppleNews::Exporter.new(elements)
69
69
  end
70
70
 
71
- # FacebookInstantArticle export of the article
71
+ # AppleNews export of the article
72
72
  # @return [String]
73
- def to_facebook_instant_article
74
- facebook_instant_article_exporter.html
73
+ def to_apple_news
74
+ apple_news_exporter.to_json
75
75
  end
76
76
 
77
77
  # Exporter instance for plain text
@@ -91,10 +91,18 @@ module ArticleJSON
91
91
  # article. If the method is called multiple times, the order of additional
92
92
  # elements is maintained.
93
93
  # @param [Object] additional_elements
94
- def place_additional_elements(additional_elements)
94
+ # @param [Class<#merge_elements>] with - The passes class's `#initialize` method needs
95
+ # to accept two lists of elements. See
96
+ # `ArticleJSON::Utils::AdditionalElementPlacer`
97
+ # for reference.
98
+ def place_additional_elements(
99
+ additional_elements,
100
+ with: ArticleJSON::Utils::AdditionalElementPlacer
101
+ )
95
102
  # Reset the `#elements` method memoization
96
103
  @elements = nil
97
104
  @additional_elements.concat(additional_elements)
105
+ @additional_element_placer_class = with
98
106
  end
99
107
 
100
108
  class << self
@@ -14,10 +14,11 @@ module ArticleJSON
14
14
  end
15
15
 
16
16
  class Configuration
17
- attr_accessor :oembed_user_agent
17
+ attr_accessor :oembed_user_agent, :facebook_token
18
18
 
19
19
  def initialize
20
20
  @oembed_user_agent = nil
21
+ @facebook_token = nil
21
22
  @custom_element_exporters = {}
22
23
  end
23
24
 
@@ -30,7 +31,7 @@ module ArticleJSON
30
31
  # @param [Symbol] exporter
31
32
  # @param [Hash[Symbol => Class]] type_class_mapping
32
33
  def register_element_exporters(exporter, type_class_mapping)
33
- valid_exporters = %i(html amp facebook_instant_article plain_text)
34
+ valid_exporters = %i(html amp apple_news plain_text)
34
35
  unless valid_exporters.include?(exporter)
35
36
  raise ArgumentError, '`exporter` needs to be one of ' \
36
37
  "#{valid_exporters} but is `#{exporter.inspect}`"
@@ -38,8 +39,9 @@ module ArticleJSON
38
39
  if !type_class_mapping.is_a?(Hash) ||
39
40
  type_class_mapping.keys.any? { |key| !key.is_a? Symbol } ||
40
41
  type_class_mapping.values.any? { |value| !value.is_a? Class }
41
- raise ArgumentError, '`type_class_mapping` has to be a Hash with '\
42
- 'symbolized keys and classes as values but is '\
42
+
43
+ raise ArgumentError, '`type_class_mapping` has to be a Hash with ' \
44
+ 'symbolized keys and classes as values but is ' \
43
45
  "`#{type_class_mapping.inspect}`"
44
46
  end
45
47
 
@@ -56,4 +58,3 @@ module ArticleJSON
56
58
  end
57
59
  end
58
60
  end
59
-
@@ -37,4 +37,3 @@ module ArticleJSON
37
37
  end
38
38
  end
39
39
  end
40
-
@@ -34,4 +34,3 @@ module ArticleJSON
34
34
  end
35
35
  end
36
36
  end
37
-
@@ -46,4 +46,3 @@ module ArticleJSON
46
46
  end
47
47
  end
48
48
  end
49
-
@@ -34,4 +34,3 @@ module ArticleJSON
34
34
  end
35
35
  end
36
36
  end
37
-
@@ -36,6 +36,7 @@ module ArticleJSON
36
36
  # @return [Integer]
37
37
  def length
38
38
  return 0 if empty?
39
+
39
40
  @content.reduce(0) do |sum, element|
40
41
  sum + (element.respond_to?(:length) ? element.length : 0)
41
42
  end
@@ -52,4 +53,3 @@ module ArticleJSON
52
53
  end
53
54
  end
54
55
  end
55
-
@@ -38,4 +38,3 @@ module ArticleJSON
38
38
  end
39
39
  end
40
40
  end
41
-
@@ -44,6 +44,7 @@ module ArticleJSON
44
44
  # @return [Integer]
45
45
  def length
46
46
  return 0 if blank?
47
+
47
48
  content.length
48
49
  end
49
50
  alias size length
@@ -63,4 +64,3 @@ module ArticleJSON
63
64
  end
64
65
  end
65
66
  end
66
-
@@ -38,4 +38,3 @@ module ArticleJSON
38
38
  end
39
39
  end
40
40
  end
41
-
@@ -54,4 +54,3 @@ module ArticleJSON
54
54
  end
55
55
  end
56
56
  end
57
-
@@ -42,18 +42,22 @@ module ArticleJSON
42
42
 
43
43
  # @return [Nokogiri::XML::Element]
44
44
  def youtube_node
45
- create_element('amp-youtube',
46
- 'data-videoid' => @element.embed_id,
47
- width: default_width,
48
- height: default_height)
45
+ create_element(
46
+ 'amp-youtube',
47
+ 'data-videoid' => @element.embed_id,
48
+ width: default_width,
49
+ height: default_height
50
+ )
49
51
  end
50
52
 
51
53
  # @return [Nokogiri::XML::Element]
52
54
  def vimeo_node
53
- create_element('amp-vimeo',
54
- 'data-videoid' => @element.embed_id,
55
- width: default_width,
56
- height: default_height)
55
+ create_element(
56
+ 'amp-vimeo',
57
+ 'data-videoid' => @element.embed_id,
58
+ width: default_width,
59
+ height: default_height
60
+ )
57
61
  end
58
62
 
59
63
  # @return [Nokogiri::XML::Element]
@@ -61,42 +65,50 @@ module ArticleJSON
61
65
  # The embed_id of a tweet is stored as "<handle>/<tweet_id>" but
62
66
  # the `amp-twitter` tag only takes the `tweet_id` part
63
67
  tweet_id = @element.embed_id.split('/').last
64
- create_element('amp-twitter',
65
- 'data-tweetid': tweet_id,
66
- width: default_width,
67
- height: default_height)
68
+ create_element(
69
+ 'amp-twitter',
70
+ 'data-tweetid': tweet_id,
71
+ width: default_width,
72
+ height: default_height
73
+ )
68
74
  end
69
75
 
70
76
  # @return [Nokogiri::XML::Element]
71
77
  def facebook_node
72
- url = "#{@element.oembed_data[:author_url]}videos/#{@element.embed_id}"
73
- create_element('amp-facebook',
74
- 'data-embedded-as' => 'video',
75
- 'data-href' => url,
76
- width: default_width,
77
- height: default_height)
78
+ url = "#{@element.oembed_data[:author_url]}/videos/#{@element.embed_id}"
79
+ create_element(
80
+ 'amp-facebook',
81
+ 'data-embedded-as' => 'video',
82
+ 'data-href' => url,
83
+ width: default_width,
84
+ height: default_height
85
+ )
78
86
  end
79
87
 
80
88
  def soundcloud_node
81
89
  src = Nokogiri::HTML(@element.oembed_data[:html])
82
- .xpath('//iframe/@src').first.value
83
- track_id = src.match(/tracks%2F(\d+)/)[1]
84
- create_element('amp-soundcloud',
85
- layout: 'fixed-height',
86
- 'data-trackid': track_id,
87
- 'data-visual': true,
88
- width: 'auto',
89
- height: default_height)
90
+ .xpath('//iframe/@src').first.value
91
+ track_id = src.match(%r{tracks%2F(\d+)})[1]
92
+ create_element(
93
+ 'amp-soundcloud',
94
+ layout: 'fixed-height',
95
+ 'data-trackid': track_id,
96
+ 'data-visual': true,
97
+ width: 'auto',
98
+ height: default_height
99
+ )
90
100
  end
91
101
 
92
102
  # @return [Nokogiri::XML::Element]
93
103
  def iframe_node
94
104
  node = Nokogiri::HTML(@element.oembed_data[:html]).xpath('//iframe')
95
- create_element('amp-iframe',
96
- src: node.attribute('src').value,
97
- width: node.attribute('width').value,
98
- height: node.attribute('height').value,
99
- frameborder: '0',)
105
+ create_element(
106
+ 'amp-iframe',
107
+ src: node.attribute('src').value,
108
+ width: node.attribute('width').value,
109
+ height: node.attribute('height').value,
110
+ frameborder: '0'
111
+ )
100
112
  end
101
113
 
102
114
  # @return [String]
@@ -9,11 +9,13 @@ module ArticleJSON
9
9
 
10
10
  # @return [Nokogiri::HTML::NodeSet]
11
11
  def image_node
12
- create_element('amp-img',
13
- src: @element.source_url,
14
- width: default_width,
15
- height: default_height,
16
- layout: :responsive)
12
+ create_element(
13
+ 'amp-img',
14
+ src: @element.source_url,
15
+ width: default_width,
16
+ height: default_height,
17
+ layout: :responsive
18
+ )
17
19
  end
18
20
 
19
21
  def default_width
@@ -8,10 +8,11 @@ module ArticleJSON
8
8
  # @return [Array[Symbol]]
9
9
  def custom_element_tags
10
10
  return @custom_element_tags if defined? @custom_element_tags
11
+
11
12
  @custom_element_tags =
12
13
  element_exporters
13
- .flat_map { |element| element.custom_element_tags }
14
- .uniq
14
+ .flat_map { |element| element.custom_element_tags }
15
+ .uniq
15
16
  end
16
17
 
17
18
  # Return an array with all the javascript libraries needed for some
@@ -19,6 +20,7 @@ module ArticleJSON
19
20
  # @return [Array<String>]
20
21
  def amp_libraries
21
22
  return @amp_libraries if defined? @amp_libraries
23
+
22
24
  @amp_libraries =
23
25
  CustomElementLibraryResolver.new(custom_element_tags).script_tags
24
26
  end
@@ -0,0 +1,53 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AppleNews
4
+ module Elements
5
+ class Base
6
+ include ArticleJSON::Export::Common::Elements::Base
7
+
8
+ # Export the given element. Dynamically looks up the right
9
+ # export-element-class, instantiates it and then calls the `#export`
10
+ # method.
11
+ # Defaults to nil, e.g. if no exporter is specified for the given
12
+ # type.
13
+ # @return [String]
14
+ def export
15
+ super || nil
16
+ end
17
+
18
+ class << self
19
+ # Return the module namespace this class and its subclasses are
20
+ # nested within.
21
+ # @return [Module]
22
+ def namespace
23
+ ArticleJSON::Export::AppleNews::Elements
24
+ end
25
+
26
+ private
27
+
28
+ # The format this exporter is returning. This is used to determine
29
+ # which custom element exporters should be applied from the
30
+ # configuration.
31
+ # @return [Symbol]
32
+ def export_format
33
+ :apple_news
34
+ end
35
+
36
+ def default_exporter_mapping
37
+ {
38
+ text: namespace::Text,
39
+ paragraph: namespace::Paragraph,
40
+ heading: namespace::Heading,
41
+ quote: namespace::Quote,
42
+ list: namespace::List,
43
+ image: namespace::Image,
44
+ embed: namespace::Embed,
45
+ text_box: namespace::TextBox,
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,130 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AppleNews
4
+ module Elements
5
+ class Embed < Base
6
+ # Embed| Embed, Caption
7
+ # @return [Hash, Array<Hash>]
8
+ def export
9
+ caption_text.nil? ? embed : [embed, caption]
10
+ end
11
+
12
+ private
13
+
14
+ # Embed
15
+ # @return [Hash]
16
+ def embed
17
+ {
18
+ role: role,
19
+ URL: source_url,
20
+ caption: caption_text,
21
+ }.compact
22
+ end
23
+
24
+ # Caption
25
+ # @return [Hash]
26
+ def caption
27
+ {
28
+ role: 'caption',
29
+ text: caption_text,
30
+ format: 'html',
31
+ layout: 'captionLayout',
32
+ textStyle: 'captionStyle',
33
+ }
34
+ end
35
+
36
+ # Get the exporter class for text elements
37
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
38
+ def text_exporter
39
+ self.class.exporter_by_type(:text)
40
+ end
41
+
42
+ # Caption Text
43
+ # @return [String]
44
+ def caption_text
45
+ return nil if role.nil? # Do not show captions for unsupported components
46
+
47
+ text.empty? ? nil : text
48
+ end
49
+
50
+ # @return [String]
51
+ def text
52
+ @element.caption.map do |child_element|
53
+ text_exporter.new(child_element)
54
+ .export
55
+ end.join
56
+ end
57
+
58
+ def role
59
+ @role ||=
60
+ case embed_type
61
+ when :youtube_video, :vimeo_video, :dailymotion_video
62
+ :embedwebvideo
63
+ when :facebook_video
64
+ :facebook_post
65
+ when :tweet
66
+ :tweet
67
+ when :slideshare
68
+ nil
69
+ when :soundcloud
70
+ nil
71
+ else
72
+ nil
73
+ end
74
+ end
75
+
76
+ def source_url
77
+ case embed_type
78
+ when :youtube_video
79
+ build_embeded_youtube_url
80
+ when :vimeo_video
81
+ build_embeded_vimeo_url
82
+ when :dailymotion_video
83
+ build_embeded_vimeo_url
84
+ when :facebook_video
85
+ build_facebook_video_url
86
+ when :tweet
87
+ build_twitter_url
88
+ when :slideshare
89
+ nil
90
+ when :soundcloud
91
+ nil
92
+ else
93
+ nil
94
+ end
95
+ end
96
+
97
+ def build_embeded_youtube_url
98
+ "https://www.youtube.com/embed/#{embed_id}"
99
+ end
100
+
101
+ def build_embeded_vimeo_url
102
+ "https://player.vimeo.com/video/#{embed_id}"
103
+ end
104
+
105
+ def build_embeded_dailymotion_url
106
+ "https://geo.dailymotion.com/player.html?video=#{embed_id}"
107
+ end
108
+
109
+ def build_facebook_video_url
110
+ username, id = embed_id.to_s.split("/", 2)
111
+ "https://www.facebook.com/#{username}/videos/#{id}"
112
+ end
113
+
114
+ def build_twitter_url
115
+ username, id = embed_id.to_s.split("/", 2)
116
+ "https://twitter.com/#{username}/status/#{id}"
117
+ end
118
+
119
+ def embed_type
120
+ @embed_type ||= @element.embed_type.to_sym
121
+ end
122
+
123
+ def embed_id
124
+ @embed_id ||= @element.embed_id.to_sym
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,32 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AppleNews
4
+ module Elements
5
+ class Heading < Base
6
+ # Headline
7
+ # @return [Hash]
8
+ def export
9
+ {
10
+ role: role,
11
+ text: @element.content,
12
+ layout: 'titleLayout',
13
+ textStyle: 'defaultTitle',
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ # The role of text component for adding a heading. (Required) Always
20
+ # one of these roles for this component: heading, heading1, heading2,
21
+ # heading3, heading4, heading5, or heading6.
22
+ # @return [String]
23
+ def role
24
+ return 'heading' if @element.level.nil?
25
+
26
+ "heading#{@element.level}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AppleNews
4
+ module Elements
5
+ class Image < Base
6
+ # Image | Image, Caption
7
+ # @return [Hash, Array<Hash>]
8
+ def export
9
+ caption_text.nil? ? image : [image, caption]
10
+ end
11
+
12
+ private
13
+
14
+ # Image
15
+ # @return [Hash]
16
+ def image
17
+ {
18
+ role: 'image',
19
+ URL: @element.source_url,
20
+ caption: caption_text,
21
+ }.compact
22
+ end
23
+
24
+ # Caption
25
+ # @return [Hash]
26
+ def caption
27
+ {
28
+ role: 'caption',
29
+ text: caption_text,
30
+ format: 'html',
31
+ layout: 'captionLayout',
32
+ textStyle: 'captionStyle',
33
+ }
34
+ end
35
+
36
+ # Get the exporter class for text elements
37
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
38
+ def text_exporter
39
+ self.class.exporter_by_type(:text)
40
+ end
41
+
42
+ # Caption Text
43
+ # @return [String]
44
+ def caption_text
45
+ text.empty? ? nil : text
46
+ end
47
+
48
+ # @return [String]
49
+ def text
50
+ @element.caption.map do |child_element|
51
+ text_exporter.new(child_element)
52
+ .export
53
+ end.join
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,67 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AppleNews
4
+ module Elements
5
+ class List < Base
6
+ # List
7
+ # @return [Hash]
8
+ def export
9
+ {
10
+ role: 'body',
11
+ text: list_text,
12
+ format: 'html',
13
+ layout: 'bodyLayout',
14
+ textStyle: 'bodyStyle',
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # Get the exporter class for text elements
21
+ #
22
+ # @return [ArticleJSON::Export::Common::HTML::Elements::<Class>]
23
+ def text_exporter
24
+ self.class.exporter_by_type(:text)
25
+ end
26
+
27
+ # When it is an unordered list wrap it in <ul></ul>
28
+ # When it is an ordered list wrap it in <ol></ol>
29
+ #
30
+ # List Text
31
+ # @return [String]
32
+ def list_text
33
+ prepend_list_tag + list + append_list_tag
34
+ end
35
+
36
+ # Each list item should be wrapped in <li></li>
37
+ #
38
+ # @return [String]
39
+ def list
40
+ @element.content.map do |paragraph_element|
41
+ line_item = paragraph_element.content.map do |text_element|
42
+ text_exporter.new(text_element).export
43
+ end.join
44
+
45
+ "<li>#{line_item}</li>"
46
+ end.join
47
+ end
48
+
49
+ # @return [String]
50
+ def prepend_list_tag
51
+ ordered_list? ? '<ol>' : '<ul>'
52
+ end
53
+
54
+ # @return [String]
55
+ def append_list_tag
56
+ ordered_list? ? '</ol>' : '</ul>'
57
+ end
58
+
59
+ # @return [Boolean]
60
+ def ordered_list?
61
+ @element.list_type == :ordered
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end