Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ronaldtse committed Oct 31, 2024
0 parents commit 697da33
Show file tree
Hide file tree
Showing 57 changed files with 4,432 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/rake.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Auto-generated by Cimas: Do not edit it manually!
# See https://github.com/metanorma/cimas
name: rake

on:
push:
branches: [ master, main ]
tags: [ v* ]
pull_request:

jobs:
rake:
uses: metanorma/ci/.github/workflows/generic-rake.yml@main
secrets:
pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Auto-generated by Cimas: Do not edit it manually!
# See https://github.com/metanorma/cimas
name: release

on:
workflow_dispatch:
inputs:
next_version:
description: |
Next release version. Possible values: x.y.z, major, minor, patch or pre|rc|etc
required: true
default: 'skip'
repository_dispatch:
types: [ do-release ]

jobs:
release:
uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
with:
next_version: ${{ github.event.inputs.next_version }}
secrets:
rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

# rspec failure tracking
.rspec_status
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--require spec_helper
8 changes: 8 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
AllCops:
TargetRubyVersion: 3.0

Style/StringLiterals:
EnforcedStyle: double_quotes

Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes
16 changes: 16 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

source "https://rubygems.org"

# Specify your gem's dependencies in moxml.gemspec
gemspec

gem "rake", "~> 13.0"

gem "rspec", "~> 3.0"

gem "rubocop", "~> 1.21"

gem "nokogiri", "~> 1.15"
gem "ox", "~> 2.14"
gem "oga", "~> 3.4"
280 changes: 280 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
= Moxml: Modular XML processing for Ruby
:toc: macro
:toclevels: 3
:toc-title: Contents
:source-highlighter: highlight.js

toc::[]

== Purpose

Moxml provides a unified XML processing interface for Ruby applications, abstracting the underlying XML library implementation. It enables:

* Consistent API across different XML libraries (Nokogiri, Ox, Oga)
* Simplified switching between XML processing engines
* Standard error handling and node manipulation patterns
* Clean separation between interface and implementation

== Quick start

Install the gem and at least one adapter:

[source,ruby]
----
# In your Gemfile
gem 'moxml'
gem 'nokogiri' # Default adapter
----

Build and manipulate XML documents:

[source,ruby]
----
require 'moxml'
# Create a context with the default adapter
context = Moxml.new
# Create a document
doc = context.create_document
# Build structure
root = doc.create_element('book')
doc.add_child(root)
# Add content with namespaces
root.add_namespace('dc', 'http://purl.org/dc/elements/1.1/')
title = doc.create_element('dc:title')
title.add_child(doc.create_text('XML Processing with Ruby'))
root.add_child(title)
# Output formatted XML
puts doc.to_xml(indent: 2)
----

== Classes and API

=== Context

The entry point for XML processing:

[source,ruby]
----
# Create with default adapter
context = Moxml.new
# Specify adapter
context = Moxml.new(:ox)
# Parse XML
doc = context.parse(xml_string)
# Create new document
doc = context.create_document
----

=== Document

Represents an XML document:

[source,ruby]
----
# Access root element
root = doc.root
# Create nodes
element = doc.create_element('name')
text = doc.create_text('content')
cdata = doc.create_cdata('<raw>')
comment = doc.create_comment('note')
pi = doc.create_processing_instruction('target', 'data')
# Create declaration
decl = doc.create_declaration('1.0', 'UTF-8', 'yes')
----

=== Element

Represents an XML element:

[source,ruby]
----
# Attributes
element['id'] = 'main'
value = element['class']
element.remove_attribute('temp')
# Namespaces
element.add_namespace('xs', 'http://www.w3.org/2001/XMLSchema')
ns = element.namespace
element.namespace = new_ns
# Content
element.text = 'content'
element.inner_html = '<child>nested</child>'
# Structure
element.add_child(node)
element.add_previous_sibling(node)
element.add_next_sibling(node)
----

=== Node

Base functionality for all XML nodes:

[source,ruby]
----
# Navigation
parent = node.parent
children = node.children
next_node = node.next_sibling
prev_node = node.previous_sibling
# Manipulation
node.remove
node.replace(other_node)
# Serialization
xml = node.to_xml
----

=== NodeSet

Collection of nodes from queries:

[source,ruby]
----
# Iteration
nodes.each { |node| process(node) }
nodes.map { |node| node.text }
# Access
first = nodes[0]
last = nodes[-1]
subset = nodes[0..2]
# Information
size = nodes.length
empty = nodes.empty?
----

== Integrated usage examples

=== Building a complex document

[source,ruby]
----
doc = Moxml.new.create_document
# Add declaration
doc.add_child(doc.create_declaration('1.0', 'UTF-8'))
# Create root with namespace
root = doc.create_element('library')
root.add_namespace(nil, 'http://example.org/library')
root.add_namespace('dc', 'http://purl.org/dc/elements/1.1/')
doc.add_child(root)
# Add books
['Ruby', 'XML'].each do |title|
book = doc.create_element('book')
# Add metadata
dc_title = doc.create_element('dc:title')
dc_title.add_child(doc.create_text(title))
book.add_child(dc_title)
# Add description
desc = doc.create_element('description')
desc.add_child(doc.create_cdata("About #{title}..."))
book.add_child(desc)
root.add_child(book)
end
----

=== Querying and modification

[source,ruby]
----
# Find nodes
books = doc.xpath('//book')
titles = doc.xpath('//dc:title',
'dc' => 'http://purl.org/dc/elements/1.1/')
# Modify matching nodes
books.each do |book|
# Add attribute
book['added'] = Time.now.iso8601
# Add child element
status = doc.create_element('status')
status.add_child(doc.create_text('available'))
book.add_child(status)
end
----

== Advanced usage examples

=== Custom error handling

[source,ruby]
----
begin
doc = context.parse(xml_string)
rescue Moxml::ParseError => e
puts "Parse error at line #{e.line}, column #{e.column}"
puts e.message
rescue Moxml::Error => e
puts "XML error: #{e.message}"
end
----

=== Thread-safe processing

[source,ruby]
----
require 'thread'
class XmlProcessor
def initialize
@mutex = Mutex.new
@context = Moxml.new
end
def process(xml)
@mutex.synchronize do
doc = @context.parse(xml)
# Modify document
doc.to_xml
end
end
end
----

=== Memory-efficient processing

[source,ruby]
----
# Process large documents
doc.xpath('//large-node').each do |node|
# Process node
node.remove
node = nil
end
GC.start
# Stream processing
File.open('large.xml') do |file|
doc = context.parse(file)
process(doc)
doc = nil
end
----

== Copyright and license

Copyright Ribose Inc.

This gem is available as open source under the terms of the BSD-2-Clause License.
12 changes: 12 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

require "rubocop/rake_task"

RuboCop::RakeTask.new

task default: %i[spec rubocop]
11 changes: 11 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "moxml"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

require "irb"
IRB.start(__FILE__)
8 changes: 8 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here
Loading

0 comments on commit 697da33

Please sign in to comment.