article_json 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/CODE_OF_CONDUCT.md +46 -0
  4. data/README.md +145 -1
  5. data/lib/article_json/article.rb +31 -2
  6. data/lib/article_json/configuration.rb +56 -0
  7. data/lib/article_json/elements/paragraph.rb +14 -0
  8. data/lib/article_json/elements/text.rb +13 -0
  9. data/lib/article_json/export/amp/custom_element_library_resolver.rb +55 -0
  10. data/lib/article_json/export/amp/elements/base.rb +36 -0
  11. data/lib/article_json/export/amp/elements/embed.rb +97 -0
  12. data/lib/article_json/export/amp/elements/heading.rb +11 -0
  13. data/lib/article_json/export/amp/elements/image.rb +30 -0
  14. data/lib/article_json/export/amp/elements/list.rb +11 -0
  15. data/lib/article_json/export/amp/elements/paragraph.rb +11 -0
  16. data/lib/article_json/export/amp/elements/quote.rb +11 -0
  17. data/lib/article_json/export/amp/elements/text.rb +11 -0
  18. data/lib/article_json/export/amp/elements/text_box.rb +11 -0
  19. data/lib/article_json/export/amp/exporter.rb +36 -0
  20. data/lib/article_json/export/common/html/elements/base.rb +111 -0
  21. data/lib/article_json/export/common/html/elements/embed.rb +34 -0
  22. data/lib/article_json/export/common/html/elements/heading.rb +23 -0
  23. data/lib/article_json/export/common/html/elements/image.rb +36 -0
  24. data/lib/article_json/export/common/html/elements/list.rb +36 -0
  25. data/lib/article_json/export/common/html/elements/paragraph.rb +29 -0
  26. data/lib/article_json/export/common/html/elements/quote.rb +32 -0
  27. data/lib/article_json/export/common/html/elements/shared/caption.rb +32 -0
  28. data/lib/article_json/export/common/html/elements/shared/float.rb +19 -0
  29. data/lib/article_json/export/common/html/elements/text.rb +57 -0
  30. data/lib/article_json/export/common/html/elements/text_box.rb +29 -0
  31. data/lib/article_json/export/common/html/exporter.rb +31 -0
  32. data/lib/article_json/export/html/elements/base.rb +14 -43
  33. data/lib/article_json/export/html/elements/embed.rb +1 -18
  34. data/lib/article_json/export/html/elements/heading.rb +1 -9
  35. data/lib/article_json/export/html/elements/image.rb +1 -23
  36. data/lib/article_json/export/html/elements/list.rb +1 -15
  37. data/lib/article_json/export/html/elements/paragraph.rb +1 -7
  38. data/lib/article_json/export/html/elements/quote.rb +1 -19
  39. data/lib/article_json/export/html/elements/text.rb +1 -34
  40. data/lib/article_json/export/html/elements/text_box.rb +1 -15
  41. data/lib/article_json/export/html/exporter.rb +6 -11
  42. data/lib/article_json/import/google_doc/html/embedded_vimeo_video_parser.rb +4 -4
  43. data/lib/article_json/import/google_doc/html/node_analyzer.rb +24 -2
  44. data/lib/article_json/import/google_doc/html/parser.rb +16 -7
  45. data/lib/article_json/import/google_doc/html/shared/caption.rb +7 -0
  46. data/lib/article_json/import/google_doc/html/text_parser.rb +4 -1
  47. data/lib/article_json/utils/additional_element_placer.rb +66 -0
  48. data/lib/article_json/utils/o_embed_resolver/facebook_video.rb +1 -1
  49. data/lib/article_json/utils/o_embed_resolver/slideshare.rb +1 -1
  50. data/lib/article_json/utils/o_embed_resolver/youtube_video.rb +1 -1
  51. data/lib/article_json/utils.rb +1 -0
  52. data/lib/article_json/version.rb +1 -1
  53. data/lib/article_json.rb +25 -2
  54. metadata +31 -6
  55. data/lib/article_json/export/html/elements/shared/caption.rb +0 -22
  56. data/lib/article_json/export/html/elements/shared/float.rb +0 -17
@@ -0,0 +1,11 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AMP
4
+ module Elements
5
+ class Quote < Base
6
+ include ArticleJSON::Export::Common::HTML::Elements::Quote
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AMP
4
+ module Elements
5
+ class Text < Base
6
+ include ArticleJSON::Export::Common::HTML::Elements::Text
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AMP
4
+ module Elements
5
+ class TextBox < Base
6
+ include ArticleJSON::Export::Common::HTML::Elements::TextBox
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module AMP
4
+ class Exporter
5
+ include ArticleJSON::Export::Common::HTML::Exporter
6
+
7
+ # List of all used custom element tags, e.g. `[:'amp-iframe']`
8
+ # @return [Array[Symbol]]
9
+ def custom_element_tags
10
+ return @custom_element_tags if defined? @custom_element_tags
11
+ @custom_element_tags =
12
+ element_exporters
13
+ .flat_map { |element| element.custom_element_tags }
14
+ .uniq
15
+ end
16
+
17
+ # Return an array with all the javascript libraries needed for some
18
+ # special AMP tags (like amp-facebook or amp-iframe)
19
+ # @return [Array<String>]
20
+ def amp_libraries
21
+ return @amp_libraries if defined? @amp_libraries
22
+ @amp_libraries =
23
+ CustomElementLibraryResolver.new(custom_element_tags).script_tags
24
+ end
25
+
26
+ class << self
27
+ # Return the module namespace this class is nested in
28
+ # @return [Module]
29
+ def namespace
30
+ ArticleJSON::Export::AMP
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,111 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Base
7
+ # Extend `base` class with `ClassMethods` upon inclusion
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ # @param [ArticleJSON::Elements::Base] element
13
+ def initialize(element)
14
+ @element = element
15
+ end
16
+
17
+ # Export a HTML node out of the given element
18
+ # Dynamically looks up the right export-element-class, instantiates it
19
+ # and then calls the #build method.
20
+ # @return [Nokogiri::XML::NodeSet]
21
+ def export
22
+ exporter.export unless exporter.nil?
23
+ end
24
+
25
+ private
26
+
27
+ # Create a Nokogiri NodeSet wrapping a Node with the given `tag`
28
+ # @param [Symbol] tag - type of the created element
29
+ # @param [*Object] args - additional arguments for the element
30
+ # @yield [Nokogiri::XML::Element] Optional block can be passed, will
31
+ # be executed with the generated
32
+ # element as a parameter
33
+ # @return [Nokogiri::XML::NodeSet] Generated main element wrapped in a
34
+ # NodeSet
35
+ def create_element(tag, *args)
36
+ document = Nokogiri::HTML.fragment('').document
37
+ element = document.create_element(tag.to_s, *args)
38
+ yield(element) if block_given?
39
+ wrap_in_node_set([element], document)
40
+ end
41
+
42
+ # Wrap a given list of Nokogiri Nodes in NodeSets
43
+ # @param [Array[Nokogiri::XML::Node]] elements
44
+ # @param [Nokogiri::HTML::Document] document
45
+ # @return [Nokogiri::XML::NodeSet]
46
+ def wrap_in_node_set(elements, document)
47
+ Nokogiri::XML::NodeSet.new(document).tap do |node_set|
48
+ elements.each { |element| node_set.push(element) }
49
+ end
50
+ end
51
+
52
+ # Get the right exporter class for the given element
53
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
54
+ def exporter
55
+ @exporter ||=
56
+ self.class.base_class? ? self.class.build(@element) : self
57
+ end
58
+
59
+ # Return the base class for the current element instance
60
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
61
+ def base_class
62
+ self.class.namespace::Base
63
+ end
64
+
65
+ module ClassMethods
66
+ # Instantiate the correct sub class for a given element
67
+ # @param [ArticleJSON::Elements::Base] element
68
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
69
+ def build(element)
70
+ klass = exporter_by_type(element.type)
71
+ klass.new(element) unless klass.nil?
72
+ end
73
+
74
+ # Look up the correct exporter class based on the element type
75
+ # @param [Symbol] type
76
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
77
+ def exporter_by_type(type)
78
+ key = type.to_sym
79
+ custom_class = ArticleJSON.configuration.element_exporter_for(
80
+ export_format, key
81
+ )
82
+ custom_class || default_exporter_mapping[key]
83
+ end
84
+
85
+ # Check if the current class is the base class a child class.
86
+ # Since this common module is in a different namespace, a simple
87
+ # `self == Base` check does not work.
88
+ # @return [Boolean]
89
+ def base_class?
90
+ self == namespace::Base
91
+ end
92
+
93
+ def default_exporter_mapping
94
+ {
95
+ text: namespace::Text,
96
+ paragraph: namespace::Paragraph,
97
+ heading: namespace::Heading,
98
+ list: namespace::List,
99
+ quote: namespace::Quote,
100
+ image: namespace::Image,
101
+ embed: namespace::Embed,
102
+ text_box: namespace::TextBox,
103
+ }
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,34 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Embed
7
+ include ArticleJSON::Export::Common::HTML::Elements::Shared::Caption
8
+
9
+ # Generate the embedded element node
10
+ # @return [Nokogiri::XML::NodeSet]
11
+ def export
12
+ create_element(:figure) do |figure|
13
+ figure.add_child(embed_node)
14
+ figure.add_child(caption_node(:figcaption))
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def embed_node
21
+ create_element(:div, class: 'embed') do |div|
22
+ div.add_child(embedded_object)
23
+ end
24
+ end
25
+
26
+ def embedded_object
27
+ Nokogiri::HTML.fragment(@element.oembed_data[:html])
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Heading
7
+ # Generate the heading element with the right text
8
+ # @return [Nokogiri::XML::NodeSet]
9
+ def export
10
+ create_element(tag_name, @element.content)
11
+ end
12
+
13
+ private
14
+
15
+ def tag_name
16
+ "h#{@element.level}".to_sym
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Image
7
+ include ArticleJSON::Export::Common::HTML::Elements::Shared::Caption
8
+ include ArticleJSON::Export::Common::HTML::Elements::Shared::Float
9
+
10
+ # Generate the `<figure>` node containing the image and caption
11
+ # @return [Nokogiri::XML::Element]
12
+ def export
13
+ create_element(:figure, node_opts) do |figure|
14
+ figure.add_child(image_node)
15
+ figure.add_child(caption_node(:figcaption))
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # @return [Nokogiri::XML::NodeSet]
22
+ def image_node
23
+ create_element(:img, src: @element.source_url)
24
+ end
25
+
26
+ # @return [Hash]
27
+ def node_opts
28
+ return if floating_class.nil?
29
+ { class: floating_class }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module List
7
+ # Generate the list node with its elements
8
+ # @return [Nokogiri::XML::NodeSet]
9
+ def export
10
+ create_element(tag_name) do |list|
11
+ @element.content.each do |child_element|
12
+ list_item_wrapper = create_element(:li) do |item|
13
+ item.add_child(paragraph_exporter.new(child_element).export)
14
+ end
15
+ list.add_child(list_item_wrapper)
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def tag_name
23
+ @element.list_type == :ordered ? :ol : :ul
24
+ end
25
+
26
+ # Get the exporter class for paragraph elements
27
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
28
+ def paragraph_exporter
29
+ self.class.exporter_by_type(:paragraph)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Paragraph
7
+ # Generate the paragraph node with its containing text elements
8
+ # @return [Nokogiri::XML::NodeSet]
9
+ def export
10
+ create_element(:p) do |p|
11
+ @element.content.each do |child_element|
12
+ p.add_child(text_exporter.new(child_element).export)
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Get the exporter class for text elements
20
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
21
+ def text_exporter
22
+ self.class.exporter_by_type(:text)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Quote
7
+ include ArticleJSON::Export::Common::HTML::Elements::Shared::Caption
8
+ include ArticleJSON::Export::Common::HTML::Elements::Shared::Float
9
+
10
+ # Generate the quote node with all its containing text elements
11
+ # @return [Nokogiri::XML::NodeSet]
12
+ def export
13
+ create_element(:div, node_opts) do |div|
14
+ @element.content.each do |child_element|
15
+ div.add_child(base_class.new(child_element).export)
16
+ end
17
+ div.add_child(caption_node(:small))
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ # @return [Hash]
24
+ def node_opts
25
+ { class: ['quote', floating_class].compact.join(' ') }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Shared
7
+ module Caption
8
+ # Generate the caption node
9
+ # @param [String] tag_name
10
+ # @return [Nokogiri::XML::NodeSet]
11
+ def caption_node(tag_name)
12
+ create_element(tag_name) do |caption|
13
+ @element.caption.each do |child_element|
14
+ caption.add_child(text_exporter.new(child_element).export)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # Get the exporter class for text elements
22
+ # @return [ArticleJSON::Export::Common::HTML::Elements::Base]
23
+ def text_exporter
24
+ self.class.exporter_by_type(:text)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Shared
7
+ module Float
8
+ # The element's floating class, if necessary
9
+ # @return [String]
10
+ def floating_class
11
+ "float-#{@element.float}" unless @element.float.nil?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,57 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module Text
7
+ # Generate a Nokogiri node or simple text node, depending on the text
8
+ # options
9
+ # @return [Nokogiri::XML::NodeSet]
10
+ def export
11
+ return bold_and_italic_node if @element.bold && @element.italic
12
+ return bold_node if @element.bold
13
+ return italic_node if @element.italic
14
+ content_node
15
+ end
16
+
17
+ private
18
+
19
+ # @return [Nokogiri::XML::NodeSet]
20
+ def italic_node
21
+ create_element(:em) { |em| em.add_child(content_node) }
22
+ end
23
+
24
+ # @return [Nokogiri::XML::NodeSet]
25
+ def bold_node
26
+ create_element(:strong) do |strong|
27
+ strong.add_child(content_node)
28
+ end
29
+ end
30
+
31
+ # @return [Nokogiri::XML::NodeSet]
32
+ def bold_and_italic_node
33
+ create_element(:strong) do |strong|
34
+ strong.add_child(italic_node)
35
+ end
36
+ end
37
+
38
+ # @return [Nokogiri::XML::NodeSet]
39
+ def content_node
40
+ return create_text_nodes(@element.content) if @element.href.nil?
41
+ create_element(:a, href: @element.href) do |a|
42
+ a.add_child(create_text_nodes(@element.content))
43
+ end
44
+ end
45
+
46
+ # @param [Nokogiri::XML::NodeSet] text
47
+ def create_text_nodes(text)
48
+ Nokogiri::HTML
49
+ .fragment(text.gsub(/\n/, '<br>'))
50
+ .children
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Elements
6
+ module TextBox
7
+ include ArticleJSON::Export::Common::HTML::Elements::Shared::Float
8
+
9
+ # Generate a `<div>` node containing all text box elements
10
+ # @return [Nokogiri::XML::NodeSet]
11
+ def export
12
+ create_element(:div, node_opts) do |div|
13
+ @element.content.each do |child_element|
14
+ div.add_child(base_class.new(child_element).export)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def node_opts
22
+ { class: ['text-box', floating_class].compact.join(' ') }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ module ArticleJSON
2
+ module Export
3
+ module Common
4
+ module HTML
5
+ module Exporter
6
+ # @param [Array[ArticleJSON::Elements::Base]] elements
7
+ def initialize(elements)
8
+ @elements = elements
9
+ end
10
+
11
+ # Generate a string with the HTML representation of all elements
12
+ # @return [String]
13
+ def html
14
+ doc = Nokogiri::HTML.fragment('')
15
+ element_exporters.each do |element_exporter|
16
+ doc.add_child(element_exporter.export)
17
+ end
18
+ doc.to_html(save_with: 0)
19
+ end
20
+
21
+ private
22
+
23
+ def element_exporters
24
+ @element_exporters ||=
25
+ @elements.map { |e| self.class.namespace::Elements::Base.build(e) }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -3,53 +3,24 @@ module ArticleJSON
3
3
  module HTML
4
4
  module Elements
5
5
  class Base
6
- # @param [ArticleJSON::Elements::Base] element
7
- def initialize(element)
8
- @element = element
9
- end
10
-
11
- # Export a HTML node out of the given element
12
- # Dynamically looks up the right export-element-class, instantiates it
13
- # and then calls the #build method.
14
- # @return [Nokogiri::HTML::Node]
15
- def export
16
- exporter = self.class == Base ? self.class.build(@element) : self
17
- exporter.export unless exporter.nil?
18
- end
19
-
20
- private
21
-
22
- def create_element(tag, *args)
23
- Nokogiri::HTML.fragment('').document.create_element(tag.to_s, *args)
24
- end
25
-
26
- def create_text_node(text)
27
- Nokogiri::HTML.fragment(text).children.first
28
- end
6
+ include ArticleJSON::Export::Common::HTML::Elements::Base
29
7
 
30
8
  class << self
31
- # Instantiate the correct sub class for a given element
32
- # @param [ArticleJSON::Elements::Base] element
33
- # @return [ArticleJSON::Export::HTML::Elements::Base]
34
- def build(element)
35
- klass = exporter_by_type(element.type)
36
- klass.new(element) unless klass.nil?
9
+ # Return the module namespace this class and its subclasses are
10
+ # nested in
11
+ # @return [Module]
12
+ def namespace
13
+ ArticleJSON::Export::HTML::Elements
37
14
  end
38
15
 
39
- # Look up the correct exporter class based on the element type
40
- # @param [Symbol] type
41
- # @return [ArticleJSON::Export::HTML::Elements::Base]
42
- def exporter_by_type(type)
43
- {
44
- text: Text,
45
- paragraph: Paragraph,
46
- heading: Heading,
47
- list: List,
48
- image: Image,
49
- text_box: TextBox,
50
- quote: Quote,
51
- embed: Embed,
52
- }[type.to_sym]
16
+ private
17
+
18
+ # The format this exporter is returning. This is used to determine
19
+ # which custom element exporters should be applied from the
20
+ # configuration.
21
+ # @return [Symbol]
22
+ def export_format
23
+ :html
53
24
  end
54
25
  end
55
26
  end
@@ -3,24 +3,7 @@ module ArticleJSON
3
3
  module HTML
4
4
  module Elements
5
5
  class Embed < Base
6
- include Shared::Caption
7
-
8
- def export
9
- create_element(:figure).tap do |figure|
10
- figure.add_child(embed_node)
11
- figure.add_child(caption_node(:figcaption))
12
- end
13
- end
14
-
15
- private
16
-
17
- def embed_node
18
- create_element(:div, embedded_object, class: 'embed')
19
- end
20
-
21
- def embedded_object
22
- "Embedded Object: #{@element.embed_type}-#{@element.embed_id}"
23
- end
6
+ include ArticleJSON::Export::Common::HTML::Elements::Embed
24
7
  end
25
8
  end
26
9
  end
@@ -3,15 +3,7 @@ module ArticleJSON
3
3
  module HTML
4
4
  module Elements
5
5
  class Heading < Base
6
- def export
7
- create_element(tag_name, @element.content)
8
- end
9
-
10
- private
11
-
12
- def tag_name
13
- "h#{@element.level}".to_sym
14
- end
6
+ include ArticleJSON::Export::Common::HTML::Elements::Heading
15
7
  end
16
8
  end
17
9
  end