mjml-rb 0.2.33 → 0.2.34

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6147ba24e9e7ede9406ebbaa48655e885f72e7f61d14c0da37ecde7ecdc66a68
4
- data.tar.gz: 6809e1ba8ee7927d565831840a5938dea5bc37abfaff3691d4b60dd4eacd67d0
3
+ metadata.gz: bae58d9f87e82dc3c39bd0c9fca4949bb2a953cf8e22a2dd56fa2d5ff057a566
4
+ data.tar.gz: b892f3ed57cf49cf3935a60cd8abef4f232544b0bb2abf2999ef5a557b49849f
5
5
  SHA512:
6
- metadata.gz: 961a93196418591ce8d2f485697ac7cdf7bd4ef2cbf2f53eac02b0811190b324e9d035c442d6d53b48e3faa94db47c44a3b32098354e36f5348ae2b4d1351372
7
- data.tar.gz: 98a28a390dda4f04d50ab4170c8e277cb7546023bcb5da0aefd242e2524a00d9beb5a91d5c28c18dc308644072911630da43437a5f2b06d96972aa3b13454175
6
+ metadata.gz: 320fa67da655fcb902557e784e8f1411ab94316b0c1d9e18b2890a6e25fba015ce159fe8ae6e404fbfa60273c9db3a0fdb45320abc9d72eba19a7eff8867706a
7
+ data.tar.gz: e6e3176e0c1cf87791bac2625ea520c339a4059f9ef474b74e16cc2ffd6bd8c1664caa35bd429e2c963d4f39167051fbaf7e391669d44cec93e7788664e5d519
@@ -1,13 +1,14 @@
1
1
  module MjmlRb
2
2
  class AstNode
3
- attr_reader :tag_name, :attributes, :children, :content, :line
3
+ attr_reader :tag_name, :attributes, :children, :content, :line, :file
4
4
 
5
- def initialize(tag_name:, attributes: {}, children: [], content: nil, line: nil)
5
+ def initialize(tag_name:, attributes: {}, children: [], content: nil, line: nil, file: nil)
6
6
  @tag_name = tag_name.to_s
7
7
  @attributes = attributes.transform_keys(&:to_s)
8
8
  @children = Array(children)
9
9
  @content = content
10
10
  @line = line
11
+ @file = file
11
12
  end
12
13
 
13
14
  def text?
@@ -33,7 +33,8 @@ module MjmlRb
33
33
  xml = normalize_html_void_tags(xml)
34
34
  xml = expand_includes(xml, opts) unless opts[:ignore_includes]
35
35
 
36
- doc = Document.new(sanitize_bare_ampersands(xml))
36
+ xml = annotate_line_numbers(sanitize_bare_ampersands(xml))
37
+ doc = Document.new(xml)
37
38
  element_to_ast(doc.root, keep_comments: opts[:keep_comments])
38
39
  rescue ParseException => e
39
40
  raise ParseError.new("XML parse error: #{e.message}")
@@ -119,6 +120,7 @@ module MjmlRb
119
120
  fragment = Document.new(sanitize_bare_ampersands("<include-root>#{replacement}</include-root>"))
120
121
  insert_before = include_node
121
122
  fragment.root.children.each do |child|
123
+ annotate_include_source(child, resolved_path) if child.is_a?(Element)
122
124
  parent.insert_before(insert_before, deep_clone(child))
123
125
  end
124
126
  parent.delete(include_node)
@@ -211,6 +213,36 @@ module MjmlRb
211
213
  content.gsub(/&(?!(?:#[0-9]+|#x[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);)/, "&amp;")
212
214
  end
213
215
 
216
+ # Adds data-mjml-line attributes to MJML tags so line numbers survive
217
+ # REXML parsing (which doesn't expose source positions).
218
+ # Skips content inside CDATA sections to avoid modifying raw HTML.
219
+ def annotate_line_numbers(xml)
220
+ line = 1
221
+ xml.gsub(/(\n)|(<!\[CDATA\[.*?\]\]>)|(<(?:mj-[\w-]+|mjml)(?=[\s\/>]))/m) do
222
+ if Regexp.last_match(1) # newline
223
+ line += 1
224
+ "\n"
225
+ elsif Regexp.last_match(2) # CDATA section — count newlines, pass through
226
+ line += Regexp.last_match(2).count("\n")
227
+ Regexp.last_match(2)
228
+ else # opening MJML tag
229
+ "#{Regexp.last_match(3)} data-mjml-line=\"#{line}\""
230
+ end
231
+ end
232
+ end
233
+
234
+ # Recursively marks REXML elements from included files with data-mjml-file.
235
+ # Only sets the attribute on elements that don't already have it (preserving
236
+ # deeper include annotations from recursive expansion).
237
+ def annotate_include_source(element, file_path)
238
+ return unless element.is_a?(Element)
239
+
240
+ if (element.name.start_with?("mj-") || element.name == "mjml") && !element.attributes["data-mjml-file"]
241
+ element.add_attribute("data-mjml-file", file_path)
242
+ end
243
+ element.each_element { |child| annotate_include_source(child, file_path) }
244
+ end
245
+
214
246
  def resolve_include_path(include_path, actual_path, file_path)
215
247
  include_path = include_path.to_s
216
248
  return include_path if File.absolute_path(include_path) == include_path && File.file?(include_path)
@@ -245,16 +277,25 @@ module MjmlRb
245
277
  def element_to_ast(element, keep_comments:)
246
278
  raise ParseError, "Missing XML root element" unless element
247
279
 
280
+ # Extract metadata annotations (added by annotate_line_numbers / annotate_include_source)
281
+ # and strip them from the public attributes hash.
282
+ meta_line = element.attributes["data-mjml-line"]&.to_i
283
+ meta_file = element.attributes["data-mjml-file"]
284
+ attrs = element.attributes.each_with_object({}) do |(name, val), h|
285
+ h[name] = val unless name.start_with?("data-mjml-")
286
+ end
287
+
248
288
  # For ending-tag elements whose content was wrapped in CDATA, store
249
289
  # the raw HTML directly as content instead of parsing structurally.
250
290
  if ENDING_TAGS_FOR_CDATA.include?(element.name)
251
291
  raw_content = element.children.select { |c| c.is_a?(Text) }.map(&:value).join
252
292
  return AstNode.new(
253
293
  tag_name: element.name,
254
- attributes: element.attributes.each_with_object({}) { |(name, val), h| h[name] = val },
294
+ attributes: attrs,
255
295
  children: [],
256
296
  content: raw_content.empty? ? nil : raw_content,
257
- line: nil
297
+ line: meta_line,
298
+ file: meta_file
258
299
  )
259
300
  end
260
301
 
@@ -272,9 +313,10 @@ module MjmlRb
272
313
 
273
314
  AstNode.new(
274
315
  tag_name: element.name,
275
- attributes: element.attributes.each_with_object({}) { |(name, val), h| h[name] = val },
316
+ attributes: attrs,
276
317
  children: children,
277
- line: nil
318
+ line: meta_line,
319
+ file: meta_file
278
320
  )
279
321
  end
280
322
  end
@@ -64,7 +64,7 @@ module MjmlRb
64
64
 
65
65
  errors << error(
66
66
  "Element <#{child.tag_name}> is not allowed inside <#{node.tag_name}>",
67
- tag_name: child.tag_name
67
+ tag_name: child.tag_name, line: child.line, file: child.file
68
68
  )
69
69
  end
70
70
  end
@@ -74,7 +74,8 @@ module MjmlRb
74
74
  required.each do |attr|
75
75
  next if node.attributes.key?(attr)
76
76
 
77
- errors << error("Attribute `#{attr}` is required for <#{node.tag_name}>", tag_name: node.tag_name)
77
+ errors << error("Attribute `#{attr}` is required for <#{node.tag_name}>",
78
+ tag_name: node.tag_name, line: node.line, file: node.file)
78
79
  end
79
80
  end
80
81
 
@@ -86,7 +87,8 @@ module MjmlRb
86
87
  next if allowed_attributes.key?(attribute_name)
87
88
  next if GLOBAL_ALLOWED_ATTRIBUTES.include?(attribute_name)
88
89
 
89
- errors << error("Attribute `#{attribute_name}` is not allowed for <#{node.tag_name}>", tag_name: node.tag_name)
90
+ errors << error("Attribute `#{attribute_name}` is not allowed for <#{node.tag_name}>",
91
+ tag_name: node.tag_name, line: node.line, file: node.file)
90
92
  end
91
93
  end
92
94
 
@@ -103,7 +105,7 @@ module MjmlRb
103
105
 
104
106
  errors << error(
105
107
  "Attribute `#{attribute_name}` on <#{node.tag_name}> has invalid value `#{attribute_value}` for type `#{expected_type}`",
106
- tag_name: node.tag_name
108
+ tag_name: node.tag_name, line: node.line, file: node.file
107
109
  )
108
110
  end
109
111
  end
@@ -183,12 +185,18 @@ module MjmlRb
183
185
  end
184
186
  end
185
187
 
186
- def error(message, line: nil, tag_name: nil)
188
+ def error(message, line: nil, tag_name: nil, file: nil)
189
+ location = [
190
+ ("line #{line}" if line),
191
+ ("in #{file}" if file)
192
+ ].compact.join(", ")
193
+
187
194
  {
188
195
  line: line,
196
+ file: file,
189
197
  message: message,
190
198
  tag_name: tag_name,
191
- formatted_message: message
199
+ formatted_message: location.empty? ? message : "#{message} (#{location})"
192
200
  }
193
201
  end
194
202
  end
@@ -1,3 +1,3 @@
1
1
  module MjmlRb
2
- VERSION = "0.2.33".freeze
2
+ VERSION = "0.2.34".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mjml-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.33
4
+ version: 0.2.34
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Andriichuk