Class: Kramdown::Parser::AtlassianDocumentFormat
- Inherits:
-
Base
- Object
- Base
- Kramdown::Parser::AtlassianDocumentFormat
- Defined in:
- lib/kramdown/parser/atlassian_document_format.rb
Overview
Parses an Atlassian Document Format (ADF) json into a Kramdown AST tree, for conversion to another format. The primary goal is to convert in GitLab Markdown.
This parser does NOT resolve external resources, such as media/attachments. A special url is generated for media based on the id, for example
![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
so that a later filter/process can resolve those.
Constant Summary collapse
- TOP_LEVEL_BLOCK_NODES =
%w[blockquote bulletList codeBlock heading mediaGroup mediaSingle orderedList panel paragraph rule table].freeze
- CHILD_BLOCK_NODES =
%w[listItem media table_cell table_header table_row].freeze
- INLINE_NODES =
%w[emoji hardBreak inlineCard mention text].freeze
- MARKS =
%w[code em link strike strong subsup textColor underline].freeze
- TABLE_CELL_NODES =
%w[blockquote bulletList codeBlock heading mediaGroup orderedList panel paragraph rule].freeze
- LIST_ITEM_NODES =
%w[bulletList codeBlock mediaSingle orderedList paragraph].freeze
- PANEL_NODES =
%w[bulletList heading orderedList paragraph].freeze
- PANEL_EMOJIS =
{ info: ':information_source:', note: ':notepad_spiral:', warning: ':warning:', success: ':white_check_mark:', error: ':octagonal_sign:' }.freeze
- CODE_BLOCK_DEFAULT_LANGUAGE =
The default language for code blocks is ‘java`, as indicated in You can’t change the default in Jira. There was a comment that indicated Confluence can set the default language.
'java'
Instance Method Summary collapse
- #parse ⇒ Object
- #process_blockquote(element, ast_node) ⇒ Object
- #process_bullet_list(element, ast_node) ⇒ Object
- #process_code_block(element, ast_node) ⇒ Object
- #process_content(element, ast_node, allowed_types) ⇒ Object
- #process_emoji(element, ast_node) ⇒ Object
- #process_hard_break(element, ast_node) ⇒ Object
- #process_heading(element, ast_node) ⇒ Object
- #process_inline_card(element, ast_node) ⇒ Object
- #process_list_item(element, ast_node) ⇒ Object
- #process_media(element, ast_node) ⇒ Object
-
#process_media_group(element, ast_node) ⇒ Object
wraps a group media element.
-
#process_media_single(element, ast_node) ⇒ Object
wraps a single media element.
- #process_mention(element, ast_node) ⇒ Object
- #process_ordered_list(element, ast_node) ⇒ Object
-
#process_panel(element, ast_node) ⇒ Object
since we don’t have something similar, then put <hr> around it and add a bolded status text (eg: “Error:”) to the front of it.
- #process_paragraph(element, ast_node) ⇒ Object
- #process_rule(element, ast_node) ⇒ Object
- #process_table(element, ast_node) ⇒ Object
-
#process_table_cell(element, ast_node) ⇒ Object
we ignore the attributes, attrs.background, attrs.colspan, attrs.colwidth, and attrs.rowspan.
-
#process_table_header(element, ast_node) ⇒ Object
we ignore the attributes, attrs.background, attrs.colspan, attrs.colwidth, and attrs.rowspan.
- #process_table_row(element, ast_node) ⇒ Object
- #process_text(element, ast_node) ⇒ Object
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args) ⇒ Object (private)
376 377 378 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 376 def method_missing(method, *args) raise NotImplementedError, "method `#{method}` not implemented yet" end |
Instance Method Details
#parse ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 87 def parse ast = Gitlab::Json.parse(@source) validate_document(ast) process_content(@root, ast, TOP_LEVEL_BLOCK_NODES) rescue ::JSON::ParserError => e msg = 'Invalid Atlassian Document Format JSON' Gitlab::AppLogger.error msg Gitlab::AppLogger.error e raise ::Kramdown::Error, msg end |
#process_blockquote(element, ast_node) ⇒ Object
109 110 111 112 113 114 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 109 def process_blockquote(element, ast_node) new_element = Element.new(:blockquote) element.children << new_element process_content(new_element, ast_node, TOP_LEVEL_BLOCK_NODES) end |
#process_bullet_list(element, ast_node) ⇒ Object
116 117 118 119 120 121 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 116 def process_bullet_list(element, ast_node) new_element = Element.new(:ul) element.children << new_element process_content(new_element, ast_node, %w[listItem]) end |
#process_code_block(element, ast_node) ⇒ Object
123 124 125 126 127 128 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 123 def process_code_block(element, ast_node) code_text = gather_text(ast_node) lang = ast_node.dig('attrs', 'language') || CODE_BLOCK_DEFAULT_LANGUAGE element.children << Element.new(:codeblock, code_text, {}, { lang: lang }) end |
#process_content(element, ast_node, allowed_types) ⇒ Object
101 102 103 104 105 106 107 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 101 def process_content(element, ast_node, allowed_types) ast_node['content'].each do |node| next unless allowed_types.include?(node['type']) public_send("process_#{node['type'].underscore}", element, node) # rubocop:disable GitlabSecurity/PublicSend end end |
#process_emoji(element, ast_node) ⇒ Object
130 131 132 133 134 135 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 130 def process_emoji(element, ast_node) emoji = ast_node.dig('attrs', 'text') || ast_node.dig('attrs', 'shortName') return unless emoji add_text(emoji, element, :text) end |
#process_hard_break(element, ast_node) ⇒ Object
137 138 139 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 137 def process_hard_break(element, ast_node) element.children << Element.new(:br) end |
#process_heading(element, ast_node) ⇒ Object
141 142 143 144 145 146 147 148 149 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 141 def process_heading(element, ast_node) level = ast_node.dig('attrs', 'level').to_i.clamp(1, 6) = { level: level } new_element = Element.new(:header, nil, nil, ) element.children << new_element process_content(new_element, ast_node, INLINE_NODES) extract_element_text(new_element, new_element.[:raw_text] = +'') end |
#process_inline_card(element, ast_node) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 151 def process_inline_card(element, ast_node) url = ast_node.dig('attrs', 'url') data = ast_node.dig('attrs', 'data') if url # we don't pull a description from the link and create a panel, # just convert to a normal link new_element = Element.new(:text, url) element.children << wrap_element(new_element, :a, nil, { 'href' => url }) elsif data # data is JSONLD (https://json-ld.org/), so for now output # as a codespan of text, with `adf-inlineCard: ` at the start text = "adf-inlineCard: #{data}" element.children << Element.new(:codespan, text, nil, { lang: 'adf-inlinecard' }) end end |
#process_list_item(element, ast_node) ⇒ Object
168 169 170 171 172 173 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 168 def process_list_item(element, ast_node) new_element = Element.new(:li) element.children << new_element process_content(new_element, ast_node, LIST_ITEM_NODES) end |
#process_media(element, ast_node) ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 175 def process_media(element, ast_node) media_url = "adf-media://#{ast_node['attrs']['id']}" case ast_node['attrs']['type'] when 'file' attrs = { 'src' => media_url, 'alt' => ast_node['attrs']['collection'] } media_element = Element.new(:img, nil, attrs) when 'link' attrs = { 'href' => media_url } media_element = wrap_element(Element.new(:text, media_url), :a, nil, attrs) end media_element = wrap_element(media_element, :p) element.children << media_element end |
#process_media_group(element, ast_node) ⇒ Object
wraps a group media element. Currently ignore attrs.layout and attrs.width
202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 202 def process_media_group(element, ast_node) ul_element = Element.new(:ul) element.children << ul_element ast_node['content'].each do |node| next unless node['type'] == 'media' li_element = Element.new(:li) ul_element.children << li_element process_media(li_element, node) end end |
#process_media_single(element, ast_node) ⇒ Object
wraps a single media element. Currently ignore attrs.layout and attrs.width
193 194 195 196 197 198 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 193 def process_media_single(element, ast_node) new_element = Element.new(:p) element.children << new_element process_content(new_element, ast_node, %w[media]) end |
#process_mention(element, ast_node) ⇒ Object
216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 216 def process_mention(element, ast_node) # Make it `@adf-mention:` since there is no guarantee that it is # a valid username in our system. This gives us an # opportunity to replace it later. Mention name can have # spaces, so double quote it mention_text = ast_node.dig('attrs', 'text')&.delete('@') mention_text = %("#{mention_text}") if mention_text&.include?(' ') mention_text = %(@adf-mention:#{mention_text}) add_text(mention_text, element, :text) end |
#process_ordered_list(element, ast_node) ⇒ Object
228 229 230 231 232 233 234 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 228 def process_ordered_list(element, ast_node) # `attrs.order` is not supported in the Kramdown AST new_element = Element.new(:ol) element.children << new_element process_content(new_element, ast_node, %w[listItem]) end |
#process_panel(element, ast_node) ⇒ Object
since we don’t have something similar, then put <hr> around it and add a bolded status text (eg: “Error:”) to the front of it.
238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 238 def process_panel(element, ast_node) panel_type = ast_node.dig('attrs', 'panelType') return unless %w[info note warning success error].include?(panel_type) panel_header_text = "#{PANEL_EMOJIS[panel_type.to_sym]} " panel_header_element = Element.new(:text, panel_header_text) new_element = Element.new(:blockquote) new_element.children << panel_header_element element.children << new_element process_content(new_element, ast_node, PANEL_NODES) end |
#process_paragraph(element, ast_node) ⇒ Object
252 253 254 255 256 257 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 252 def process_paragraph(element, ast_node) new_element = Element.new(:p) element.children << new_element process_content(new_element, ast_node, INLINE_NODES) end |
#process_rule(element, ast_node) ⇒ Object
259 260 261 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 259 def process_rule(element, ast_node) element.children << Element.new(:hr) end |
#process_table(element, ast_node) ⇒ Object
263 264 265 266 267 268 269 270 271 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 263 def process_table(element, ast_node) table = Element.new(:table, nil, nil, { alignment: [:default, :default] }) element.children << table tbody = Element.new(:tbody) table.children << tbody process_content(tbody, ast_node, %w[tableRow]) end |
#process_table_cell(element, ast_node) ⇒ Object
we ignore the attributes, attrs.background, attrs.colspan, attrs.colwidth, and attrs.rowspan
275 276 277 278 279 280 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 275 def process_table_cell(element, ast_node) new_element = Element.new(:td) element.children << new_element process_content(new_element, ast_node, TABLE_CELL_NODES) end |
#process_table_header(element, ast_node) ⇒ Object
we ignore the attributes, attrs.background, attrs.colspan, attrs.colwidth, and attrs.rowspan
284 285 286 287 288 289 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 284 def process_table_header(element, ast_node) new_element = Element.new(:th) element.children << new_element process_content(new_element, ast_node, TABLE_CELL_NODES) end |
#process_table_row(element, ast_node) ⇒ Object
291 292 293 294 295 296 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 291 def process_table_row(element, ast_node) new_element = Element.new(:tr) element.children << new_element process_content(new_element, ast_node, %w[tableHeader tableCell]) end |
#process_text(element, ast_node) ⇒ Object
298 299 300 301 302 |
# File 'lib/kramdown/parser/atlassian_document_format.rb', line 298 def process_text(element, ast_node) new_element = Element.new(:text, ast_node['text']) new_element = apply_marks(new_element, ast_node, MARKS) element.children << new_element end |