article_json 0.1.0 → 0.2.0

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 (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