Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
matrix:
ruby:
- '3.2.2'
- '3.4.5'

steps:
- uses: actions/checkout@v4
Expand All @@ -23,5 +24,9 @@ jobs:
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run the default task
run: bundle exec rake
- name: Run setup
run: bin/setup
- name: Run tests
run: bundle exec rake test
- name: Run lint
run: bundle exec rake standard
2 changes: 1 addition & 1 deletion .standard.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# For available configuration options, see:
# https://github.com/standardrb/standard
ruby_version: 2.6
ruby_version: 3.2
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 3.4.5
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Build Commands
- Setup: bin/setup
- Test all: rake test
- Test single: ruby -I test test/dast_document_test.rb --name <test_method>
- Lint: rake standard

# Code Style
- frozen_string_literal: true at file top
- require_relative for internal requires
- StandardRB linting (ruby 2.6+)
- RBS type signatures required
- Minitest for testing
- Nokogiri for HTML parsing
- Use &. safe navigation, then chaining
- Descriptive test method names
51 changes: 49 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
## [Unreleased]
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.3.0] - 2025-12-19

### Added
- Comprehensive edge case tests for DastDocument rendering:
- Empty documents, invalid node types, and missing data handling.
- Text formatting with multiple marks, unknown marks, and newlines.
- Advanced node types: blockquotes, links, unordered lists.
- Block rendering edge cases: multiple blocks, missing components/view contexts.
- HTML output validation via `to_html` method.

### Changed
- Bump required Ruby version from >= 3.2.2 to >= 3.4.5
- Update Nokogiri dependency from >= 1.13.4 to >= 1.18.9
- Add REXML >= 3.3.9 as runtime dependency

### Security
- Address multiple CVEs in Nokogiri (e.g., libxml2/libxslt vulnerabilities)
- Address REXML DoS vulnerabilities by requiring Ruby >= 3.4.5 or REXML >= 3.3.9

## [1.2.0] - 2024-02-22

### Added
- Support for ordered lists and horizontal rules in HTML output.
- AGENTS.md file with project information and build commands.

### Changed
- Major library rewrite to support passing blocks and custom components for enhanced rendering flexibility.
- Removed requirement for "Component" suffix in block names, simplifying component naming conventions.
- Improved newline handling in text content for more accurate HTML rendering.
- Code cleanup and refactoring for better maintainability.
- Added RuboCop configuration for improved code style and linting.

### Fixed
- Added null checks to prevent runtime errors during document processing.
- Corrected data passing in block rendering to ensure components receive accurate content.
- Minor adjustments to block rendering functionality for improved reliability.
- Fixed broken test case to maintain test suite integrity.

## [0.1.0] - 2024-01-31

- Initial release
### Added
- Initial release with basic HTML rendering and structured document support.

[1.3.0]: https://github.com/paradem/dast_document/releases/tag/v1.3.0
[1.2.0]: https://github.com/paradem/dast_document/releases/tag/v1.2.0
17 changes: 10 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
PATH
remote: .
specs:
dast_document (0.1.0)
nokogiri (>= 1.13.4)
dast_document (1.3.0)
nokogiri (>= 1.18.9)
ostruct (>= 0.6.0)
rexml (>= 3.3.9)

GEM
remote: https://rubygems.org/
Expand All @@ -11,22 +13,23 @@ GEM
json (2.7.1)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
mini_portile2 (2.8.5)
mini_portile2 (2.8.9)
minitest (5.21.2)
nokogiri (1.16.0)
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.0-arm64-darwin)
nokogiri (1.18.10-arm64-darwin)
racc (~> 1.4)
ostruct (0.6.3)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
racc (1.7.3)
racc (1.8.1)
rainbow (3.1.1)
rake (13.1.0)
regexp_parser (2.9.0)
rexml (3.2.6)
rexml (3.4.4)
rubocop (1.59.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
Expand Down
114 changes: 104 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,118 @@
# DastDocument

TODO: Delete this and the text below, and describe your gem

Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dast_document`. To experiment with that code, run `bin/console` for an interactive prompt.
A Ruby gem for rendering DatoCMS DAST (DatoCMS Abstract Syntax Tree) documents into HTML. Supports rich text elements (headings, paragraphs, lists) and embedded blocks with custom components.

## Table of Contents

- [Installation](#installation)
- [Usage](#usage)
- [Basic Document Rendering](#basic-document-rendering)
- [Text Formatting and Marks](#text-formatting-and-marks)
- [Links and Blockquotes](#links-and-blockquotes)
- [Blocks and Custom Components](#blocks-and-custom-components)
- [Edge Cases and Error Handling](#edge-cases-and-error-handling)
- [Supported Node Types](#supported-node-types)
- [Development](#development)
- [Contributing](#contributing)
- [License](#license)

## Installation

TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.

Install the gem and add to the application's Gemfile by executing:

$ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
$ bundle add dast_document

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
$ gem install dast_document

## Usage

TODO: Write usage instructions here
### Basic Document Rendering

Basic usage for rendering a DAST document into HTML:

```ruby
require 'dast_document'

dast = { "value" => { "document" => { "type" => "root", "children" => [
{ "type" => "paragraph", "children" => [{ "type" => "span", "value" => "Hello world!" }] },
{ "type" => "heading", "level" => 1, "value" => "Title" },
{ "type" => "list", "style" => "numbered", "children" => [
{ "type" => "listItem", "children" => [{ "type" => "paragraph", "children" => [{ "type" => "span", "value" => "Item 1" }] }] }
] }
] } }, "blocks" => [] }
document = DastDocument::Document.new(dast)
html = document.to_html
# Output: <p>Hello world!</p><h1>Title</h1><ol><li><p>Item 1</p></li></ol>
```

Note: Newlines in text content are automatically converted to `<br>` tags (e.g., "line1\n\nline2" becomes "line1<br>line2").

### Text Formatting and Marks

Apply rich text formatting using marks on spans:

```ruby
dast = { "value" => { "document" => { "type" => "root", "children" => [
{ "type" => "paragraph", "children" => [
{ "type" => "span", "value" => "Bold and italic", "marks" => ["emphasis", "underline"] }
] }
] } }, "blocks" => [] }
document = DastDocument::Document.new(dast)
html = document.to_html
# Output: <p><em><u>Bold and italic</u></em></p>
```

Unknown marks fall back to using the mark name as the HTML tag.

### Links and Blockquotes

Render links and blockquotes:

```ruby
dast = { "value" => { "document" => { "type" => "root", "children" => [
{ "type" => "link", "url" => "https://example.com" },
{ "type" => "blockquote", "attribution" => "Author Name", "children" => [
{ "type" => "paragraph", "children" => [{ "type" => "span", "value" => "Quote text" }] }
] }
] } }, "blocks" => [] }
document = DastDocument::Document.new(dast)
html = document.to_html
# Output: <a href="https://example.com"></a><figure><blockquote><p>Quote text</p></blockquote><figcaption>Author Name</figcaption></figure>
```

### Blocks and Custom Components

For blocks with custom components, provide a view context and component module:

```ruby
# Assuming Rails view context and component module
document = DastDocument::Document.new(dast, view_context: self, component_module: Components)
```

If a component is missing or view context lacks a render method, error messages are displayed in the HTML output (e.g., "Can't render block ComponentName no component defined").

### Edge Cases and Error Handling

The gem handles malformed or missing data gracefully:
- Empty documents or missing node types are skipped without errors.
- Invalid inputs (e.g., non-integer heading levels) are rendered as-is or with defaults.
- Unknown node types are ignored, ensuring robust rendering.

### Supported Node Types

| Node Type | Description | Example Output |
|----------------|--------------------------------------|----------------|
| paragraph | Text paragraph | `<p>Text</p>` |
| heading | Headings (levels 1-6) | `<h1>Title</h1>` |
| list | Ordered/unordered lists | `<ol><li>Item</li></ol>` or `<ul>` |
| listItem | List items | `<li>Item</li>` |
| span | Text with marks | `<span>Text</span>` or `<em>Text</em>` |
| link | Links | `<a href="url"></a>` |
| blockquote | Blockquotes with attribution | `<figure><blockquote>Text</blockquote><figcaption>Attr</figcaption></figure>` |
| thematicBreak | Horizontal rules | `<hr>` |
| block | Embedded blocks with components | Custom component HTML |

## Development

Expand All @@ -28,8 +122,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dast_document.
Bug reports and pull requests are welcome on GitHub at https://github.com/paradem/dast_document.

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
4 changes: 3 additions & 1 deletion dast_document.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_runtime_dependency "nokogiri", ">= 1.13.4"
spec.add_runtime_dependency "nokogiri", ">= 1.18.9"
spec.add_runtime_dependency "ostruct", ">= 0.6.0"
spec.add_runtime_dependency "rexml", ">= 3.3.9"

# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
Expand Down
2 changes: 2 additions & 0 deletions lib/dast_document.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require "nokogiri"

require_relative "dast_document/version"
Expand Down
3 changes: 3 additions & 0 deletions lib/dast_document/block_wrapper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# frozen_string_literal: true

module DastDocument
class BlockWrapper
attr_reader :content

def initialize(content)
@content = content
end
Expand Down
2 changes: 1 addition & 1 deletion lib/dast_document/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module DastDocument
VERSION = "1.2.0"
VERSION = "1.3.0"
end
32 changes: 30 additions & 2 deletions sig/dast_document.rbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
module DastDocument
VERSION: String
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
end

class Error < StandardError
end

class Document
def initialize: (untyped dast, ?view_context: untyped, ?component_module: Module) -> void
def to_html: -> String
def walk: -> untyped
def _walk: (untyped doc, untyped dast) -> untyped
def component_defined?: (String component) -> bool
def build_tag: (untyped node) -> untyped
def build_block: (String id) -> String
def build_list: (untyped node) -> String
def children: (untyped dast_node) -> void
def build_a: (String url) -> String
def build_blockquote: (String attribution) -> String
def build_node: (String tag_name, untyped value, ?untyped marks) -> String
def markup: (untyped value, untyped marks) -> untyped
def t: (String mark) -> String
end

class BlockWrapper
attr_reader content: untyped
def initialize: (untyped content) -> void
def nil?: -> bool
def id: -> untyped
def name: -> untyped
def component_name: -> String
end
end
Loading