README.adoc in moxml-0.1.0 vs README.adoc in moxml-0.1.1

- old
+ new

@@ -1,770 +1,576 @@ -= Moxml: Modular XML processing for Ruby += Moxml: Modern XML processing for Ruby +:toc: macro +:toclevels: 3 +:toc-title: Contents +:source-highlighter: highlight.js -Moxml provides a unified API for XML processing in Ruby, supporting multiple XML parsing backends (Nokogiri, Ox, and Oga). +image:https://github.com/lutaml/moxml/workflows/rake/badge.svg["Build Status", link="https://github.com/lutaml/moxml/actions?workflow=rake"] -Moxml ("mox-em-el") stands for "Modular XML" and aims to provide a consistent -interface for working with XML documents, regardless of the underlying XML -library. +toc::[] -== Installation +== Introduction and purpose +Moxml provides a unified, modern XML processing interface for Ruby applications. +It offers a consistent API that abstracts away the underlying XML implementation +details while maintaining high performance through efficient node mapping and +native XPath querying. + +Key features: + +* Intuitive, Ruby-idiomatic API for XML manipulation +* Consistent interface across different XML libraries +* Efficient node mapping for XPath queries +* Support for all XML node types and features +* Easy switching between XML processing engines +* Clean separation between interface and implementation + +== Getting started + +Install the gem and at least one supported XML library: + [source,ruby] ---- +# In your Gemfile gem 'moxml' +gem 'nokogiri' # Or 'ox' or 'oga' ---- -== Basic usage +=== Basic document creation -=== Configuration - -Configure Moxml to use your preferred XML backend: - [source,ruby] ---- require 'moxml' -Moxml.configure do |config| - config.backend = :nokogiri # or :ox, :oga -end ----- +# Create a new XML document +doc = Moxml.new.create_document -=== Creating and parsing documents +# Add XML declaration +doc.add_declaration(version: "1.0", encoding: "UTF-8") -[source,ruby] ----- -# Create new empty document -doc = Moxml::Document.new +# Create root element with namespace +root = doc.create_element('book') +root.add_namespace('dc', 'http://purl.org/dc/elements/1.1/') +doc.add_child(root) -# Parse from string -doc = Moxml::Document.parse("<root><child>content</child></root>") +# Add content +title = doc.create_element('dc:title') +title.text = 'XML Processing with Ruby' +root.add_child(title) -# Parse with encoding -doc = Moxml::Document.parse(xml_string, encoding: 'UTF-8') +# Output formatted XML +puts doc.to_xml(indent: 2) ---- -=== Document creation patterns +== Working with documents +=== Using the builder pattern + +The builder pattern provides a clean DSL for creating XML documents: + [source,ruby] ---- -# Method 1: Create and build -doc = Moxml::Document.new -root = doc.create_element('root') -doc.add_child(root) +doc = Moxml.new.build do + declaration version: "1.0", encoding: "UTF-8" -# Method 2: Parse from string -doc = Moxml::Document.parse("<root/>") + element 'library', xmlns: 'http://example.org/library' do + element 'book' do + element 'title' do + text 'Ruby Programming' + end -# Method 3: Parse with encoding -doc = Moxml::Document.parse(xml_string, encoding: 'UTF-8') + element 'author' do + text 'Jane Smith' + end -# Method 4: Parse with options -doc = Moxml::Document.parse(xml_string, { - encoding: 'UTF-8', - strict: true -}) + comment 'Publication details' + element 'published', year: '2024' + + cdata '<custom>metadata</custom>' + end + end +end ---- -=== Common XML patterns +=== Direct document manipulation [source,ruby] ---- -# Working with namespaces -doc = Moxml::Document.new -root = doc.create_element('root') -root['xmlns:custom'] = 'http://example.com/ns' -child = doc.create_element('custom:element') -root.add_child(child) +doc = Moxml.new.create_document -# Creating structured data -person = doc.create_element('person') -person['id'] = '123' -name = doc.create_element('name') -name.add_child(doc.create_text('John Doe')) -person.add_child(name) +# Add declaration +doc.add_declaration(version: "1.0", encoding: "UTF-8") -# Working with attributes -element = doc.create_element('div') -element['class'] = 'container' -element['data-id'] = '123' -element['style'] = 'color: blue' +# Create root with namespace +root = doc.create_element('library') +root.add_namespace(nil, 'http://example.org/library') +doc.add_child(root) -# Handling special characters -text = doc.create_text('Special chars: < > & " \'') -cdata = doc.create_cdata('<script>alert("Hello!");</script>') +# Add elements with attributes +book = doc.create_element('book') +book['id'] = 'b1' +root.add_child(book) -# Processing instructions -pi = doc.create_processing_instruction('xml-stylesheet', - 'type="text/xsl" href="style.xsl"') -doc.add_child(pi) +# Add mixed content +book.add_child(doc.create_comment('Book details')) +title = doc.create_element('title') +title.text = 'Ruby Programming' +book.add_child(title) ---- -=== Working with elements +== XML objects and their methods +=== Document object + +The Document object represents an XML document and serves as the root container +for all XML nodes. + [source,ruby] ---- -# Create new element -element = Moxml::Element.new('tagname') +# Creating a document +doc = Moxml.new.create_document +doc = Moxml.new.parse(xml_string) -# Add attributes -element['class'] = 'content' +# Document properties and methods +doc.encoding # Get document encoding +doc.encoding = "UTF-8" # Set document encoding +doc.version # Get XML version +doc.version = "1.1" # Set XML version +doc.standalone # Get standalone declaration +doc.standalone = "yes" # Set standalone declaration -# Access attributes -class_attr = element['class'] +# Document structure +doc.root # Get root element +doc.children # Get all top-level nodes +doc.add_child(node) # Add a child node +doc.remove_child(node) # Remove a child node -# Add child elements -child = element.create_element('child') -element.add_child(child) +# Node creation methods +doc.create_element(name) # Create new element +doc.create_text(content) # Create text node +doc.create_cdata(content) # Create CDATA section +doc.create_comment(content) # Create comment +doc.create_processing_instruction(target, content) # Create PI -# Access text content -text_content = element.text +# Document querying +doc.xpath(expression) # Find nodes by XPath +doc.at_xpath(expression) # Find first node by XPath -# Add text content -text = element.create_text('content') -element.add_child(text) - -# Chaining operations -element - .add_child(doc.create_element('child')) - .add_child(doc.create_text('content')) - ['class'] = 'new-class' - -# Complex element creation -div = doc.create_element('div') -div['class'] = 'container' -div.add_child(doc.create_element('span')) - .add_child(doc.create_text('Hello')) -div.add_child(doc.create_element('br')) -div.add_child(doc.create_text('World')) +# Serialization +doc.to_xml(options) # Convert to XML string ---- -=== Working with different node types +=== Element object +Elements are the primary structural components of an XML document, representing +tags with attributes and content. + [source,ruby] ---- -# Text nodes with various content -plain_text = Moxml::Text.new("Simple text") -multiline_text = Moxml::Text.new("Line 1\nLine 2") -special_chars = Moxml::Text.new("Special: & < > \" '") +# Element properties +element.name # Get element name +element.name = "new_name" # Set element name +element.text # Get text content +element.text = "content" # Set text content +element.inner_html # Get inner XML content +element.inner_html = xml # Set inner XML content -# CDATA sections for different content types -script_cdata = Moxml::Cdata.new("function() { alert('Hello!'); }") -xml_cdata = Moxml::Cdata.new("<data><item>value</item></data>") -mixed_cdata = Moxml::Cdata.new("Text with ]]> characters") +# Attributes +element[name] # Get attribute value +element[name] = value # Set attribute value +element.attributes # Get all attributes +element.remove_attribute(name) # Remove attribute -# Comments for documentation -todo_comment = Moxml::Comment.new("TODO: Add validation") -section_comment = Moxml::Comment.new("----- Section Break -----") -debug_comment = Moxml::Comment.new("DEBUG: Remove in production") +# Namespace handling +element.namespace # Get element's namespace +element.namespace = ns # Set element's namespace +element.add_namespace(prefix, uri) # Add new namespace +element.namespaces # Get all namespace definitions -# Processing instructions for various uses -style_pi = Moxml::ProcessingInstruction.new( - "xml-stylesheet", - 'type="text/css" href="style.css"' -) -php_pi = Moxml::ProcessingInstruction.new( - "php", - 'echo "<?php echo $var; ?>>";' -) -custom_pi = Moxml::ProcessingInstruction.new( - "custom-processor", - 'param1="value1" param2="value2"' -) +# Node structure +element.parent # Get parent node +element.children # Get child nodes +element.add_child(node) # Add child node +element.remove_child(node) # Remove child node +element.add_previous_sibling(node) # Add sibling before +element.add_next_sibling(node) # Add sibling after +element.replace(node) # Replace with another node +element.remove # Remove from document + +# Node type checking +element.element? # Returns true +element.text? # Returns false +element.cdata? # Returns false +element.comment? # Returns false +element.processing_instruction? # Returns false + +# Node querying +element.xpath(expression) # Find nodes by XPath +element.at_xpath(expression) # Find first node by XPath ---- -=== Element manipulation examples +=== Text object +Text nodes represent character data in the XML document. + [source,ruby] ---- -# Building complex structures -doc = Moxml::Document.new -root = doc.create_element('html') -doc.add_child(root) +# Creating text nodes +text = doc.create_text("content") -# Create head section -head = doc.create_element('head') -root.add_child(head) +# Text properties +text.content # Get text content +text.content = "new" # Set text content -title = doc.create_element('title') -title.add_child(doc.create_text('Example Page')) -head.add_child(title) +# Node type checking +text.text? # Returns true -meta = doc.create_element('meta') -meta['charset'] = 'UTF-8' -head.add_child(meta) +# Structure +text.parent # Get parent node +text.remove # Remove from document +text.replace(node) # Replace with another node +---- -# Create body section -body = doc.create_element('body') -root.add_child(body) +=== CDATA object -div = doc.create_element('div') -div['class'] = 'container' -body.add_child(div) +CDATA sections contain text that should not be parsed as markup. -# Add multiple paragraphs -3.times do |i| - p = doc.create_element('p') - p.add_child(doc.create_text("Paragraph #{i + 1}")) - div.add_child(p) -end +[source,ruby] +---- +# Creating CDATA sections +cdata = doc.create_cdata("<raw>content</raw>") -# Working with lists -ul = doc.create_element('ul') -div.add_child(ul) +# CDATA properties +cdata.content # Get CDATA content +cdata.content = "new" # Set CDATA content -['Item 1', 'Item 2', 'Item 3'].each do |text| - li = doc.create_element('li') - li.add_child(doc.create_text(text)) - ul.add_child(li) -end +# Node type checking +cdata.cdata? # Returns true -# Adding link element -a = doc.create_element('a') -a['href'] = 'https://example.com' -a.add_child(doc.create_text('Visit Example')) -div.add_child(a) +# Structure +cdata.parent # Get parent node +cdata.remove # Remove from document +cdata.replace(node) # Replace with another node ---- -=== Advanced node manipulation +=== Comment object +Comments contain human-readable notes in the XML document. + [source,ruby] ---- -# Cloning nodes -original = doc.create_element('div') -original['id'] = 'original' -clone = original.clone +# Creating comments +comment = doc.create_comment("Note") -# Moving nodes -target = doc.create_element('target') -source = doc.create_element('source') -source.add_child(doc.create_text('Content')) -target.add_child(source) +# Comment properties +comment.content # Get comment content +comment.content = "new" # Set comment content -# Replacing nodes -old_node = doc.at_xpath('//old') -new_node = doc.create_element('new') -old_node.replace(new_node) +# Node type checking +comment.comment? # Returns true -# Inserting before/after -reference = doc.create_element('reference') -before = doc.create_element('before') -after = doc.create_element('after') -reference.add_previous_sibling(before) -reference.add_next_sibling(after) - -# Conditional manipulation -element = doc.at_xpath('//conditional') -if element['flag'] == 'true' - element.add_child(doc.create_text('Flag is true')) -else - element.remove -end +# Structure +comment.parent # Get parent node +comment.remove # Remove from document +comment.replace(node) # Replace with another node ---- -=== Working with namespaces +=== Processing instruction object +Processing instructions provide instructions to applications processing the XML. + [source,ruby] ---- -# Creating namespaced document -doc = Moxml::Document.new -root = doc.create_element('root') -root['xmlns'] = 'http://example.com/default' -root['xmlns:custom'] = 'http://example.com/custom' -doc.add_child(root) +# Creating processing instructions +pi = doc.create_processing_instruction("xml-stylesheet", + 'type="text/xsl" href="style.xsl"') -# Adding namespaced elements -default_elem = doc.create_element('default-elem') -custom_elem = doc.create_element('custom:elem') +# PI properties +pi.target # Get PI target +pi.target = "new" # Set PI target +pi.content # Get PI content +pi.content = "new" # Set PI content -root.add_child(default_elem) -root.add_child(custom_elem) +# Node type checking +pi.processing_instruction? # Returns true -# Working with attributes in namespaces -custom_elem['custom:attr'] = 'value' - -# Accessing namespaced content -ns_elem = doc.at_xpath('//custom:elem') -ns_attr = ns_elem['custom:attr'] +# Structure +pi.parent # Get parent node +pi.remove # Remove from document +pi.replace(node) # Replace with another node ---- -=== Document serialization examples +=== Attribute object +Attributes represent name-value pairs on elements. + [source,ruby] ---- -# Basic serialization -xml_string = doc.to_xml +# Attribute properties +attr.name # Get attribute name +attr.name = "new" # Set attribute name +attr.value # Get attribute value +attr.value = "new" # Set attribute value -# Pretty printing with indentation -formatted_xml = doc.to_xml( - indent: 2, - pretty: true -) +# Namespace handling +attr.namespace # Get attribute's namespace +attr.namespace = ns # Set attribute's namespace -# Controlling XML declaration -with_declaration = doc.to_xml( - xml_declaration: true, - encoding: 'UTF-8', - standalone: 'yes' -) - -# Compact output -minimal_xml = doc.to_xml( - indent: 0, - pretty: false, - xml_declaration: false -) - -# Custom formatting -custom_format = doc.to_xml( - indent: 4, - encoding: 'ISO-8859-1', - xml_declaration: true -) +# Node type checking +attr.attribute? # Returns true ---- -== Implementation details +=== Namespace object -=== Memory management +Namespaces define XML namespaces used in the document. [source,ruby] ---- -# Efficient document handling -doc = Moxml::Document.parse(large_xml) -begin - # Process document - result = process_document(doc) -ensure - # Clear references - doc = nil - GC.start -end +# Namespace properties +ns.prefix # Get namespace prefix +ns.uri # Get namespace URI -# Streaming large node sets -doc.xpath('//large-set/*').each do |node| - # Process node - process_node(node) - # Clear reference - node = nil -end +# Formatting +ns.to_s # Format as xmlns declaration -# Handling large collections -def process_large_nodeset(nodeset) - nodeset.each do |node| - yield node if block_given? - end -ensure - # Clear references - nodeset = nil - GC.start -end +# Node type checking +ns.namespace? # Returns true ---- -=== Backend-specific optimizations +=== Node traversal and inspection +Each node type provides methods for traversing the document structure: + [source,ruby] ---- -# Nokogiri-specific optimizations -if Moxml.config.backend == :nokogiri - # Use native CSS selectors - nodes = doc.native.css('complex > selector') - nodes.each do |native_node| - node = Moxml::Node.wrap(native_node) - # Process node - end +node.parent # Get parent node +node.children # Get child nodes +node.next_sibling # Get next sibling +node.previous_sibling # Get previous sibling +node.ancestors # Get all ancestor nodes +node.descendants # Get all descendant nodes - # Use native XPath - results = doc.native.xpath('//complex/xpath/expression') -end +# Type checking +node.element? # Is it an element? +node.text? # Is it a text node? +node.cdata? # Is it a CDATA section? +node.comment? # Is it a comment? +node.processing_instruction? # Is it a PI? +node.attribute? # Is it an attribute? +node.namespace? # Is it a namespace? -# Ox-specific optimizations -if Moxml.config.backend == :ox - # Use native parsing options - doc = Moxml::Document.parse(xml, { - mode: :generic, - effort: :tolerant, - smart: true - }) +# Node information +node.document # Get owning document +node.path # Get XPath to node +node.line_number # Get source line number (if available) +---- - # Direct element creation - element = Ox::Element.new('name') - wrapped = Moxml::Element.new(element) -end +== Advanced features -# Oga-specific optimizations -if Moxml.config.backend == :oga - # Use native parsing features - doc = Moxml::Document.parse(xml, { - encoding: 'UTF-8', - strict: true - }) +=== XPath querying and node mapping - # Direct access to native methods - nodes = doc.native.xpath('//element') -end ----- +Moxml provides efficient XPath querying by leveraging the native XML library's +implementation while maintaining consistent node mapping: -=== Threading patterns - [source,ruby] ---- -# Thread-safe document creation -require 'thread' +# Find all book elements +books = doc.xpath('//book') +# Returns Moxml::Element objects mapped to native nodes -class ThreadSafeXmlProcessor - def initialize - @mutex = Mutex.new - end +# Find with namespaces +titles = doc.xpath('//dc:title', + 'dc' => 'http://purl.org/dc/elements/1.1/') - def process_document(xml_string) - @mutex.synchronize do - doc = Moxml::Document.parse(xml_string) - # Process document - result = doc.to_xml - doc = nil - result - end - end -end +# Find first matching node +first_book = doc.at_xpath('//book') -# Parallel document processing -def process_documents(xml_strings) - threads = xml_strings.map do |xml| - Thread.new do - doc = Moxml::Document.parse(xml) - # Process document - doc = nil - end - end - threads.each(&:join) +# Chain queries +doc.xpath('//book').each do |book| + # Each book is a mapped Moxml::Element + title = book.at_xpath('.//title') + puts "#{book['id']}: #{title.text}" end - -# Thread-local document storage -Thread.new do - Thread.current[:document] = Moxml::Document.new - # Process document -ensure - Thread.current[:document] = nil -end ---- -== Troubleshooting +=== Namespace handling -=== Common issues and solutions - -==== Parsing errors - [source,ruby] ---- -# Handle malformed XML -begin - doc = Moxml::Document.parse(xml_string) -rescue Moxml::ParseError => e - puts "Parse error at line #{e.line}, column #{e.column}: #{e.message}" - # Attempt recovery - xml_string = cleanup_xml(xml_string) - retry -end +# Add namespace to element +element.add_namespace('dc', 'http://purl.org/dc/elements/1.1/') -# Handle encoding issues -begin - doc = Moxml::Document.parse(xml_string, encoding: 'UTF-8') -rescue Moxml::ParseError => e - if e.message =~ /encoding/ - # Try detecting encoding - detected_encoding = detect_encoding(xml_string) - retry if detected_encoding - end - raise -end +# Create element in namespace +title = doc.create_element('dc:title') +title.text = 'Document Title' + +# Query with namespaces +doc.xpath('//dc:title', + 'dc' => 'http://purl.org/dc/elements/1.1/') ---- -==== Memory issues +=== Accessing native implementation +While not typically needed, you can access the underlying XML library's nodes: + [source,ruby] ---- -# Handle large documents -def process_large_document(path) - # Read and process in chunks - File.open(path) do |file| - doc = Moxml::Document.parse(file) - doc.xpath('//chunk').each do |chunk| - process_chunk(chunk) - chunk = nil - end - doc = nil - end - GC.start -end +# Get native node +native_node = element.native -# Monitor memory usage -require 'get_process_mem' +# Get adapter being used +adapter = element.context.config.adapter -def memory_safe_processing(xml) - memory = GetProcessMem.new - initial_memory = memory.mb +# Create from native node +element = Moxml::Element.new(native_node, context) +---- - doc = Moxml::Document.parse(xml) - result = process_document(doc) - doc = nil - GC.start +== Error handling - final_memory = memory.mb - puts "Memory usage: #{final_memory - initial_memory}MB" +Moxml provides specific error classes for different types of errors that may +occur during XML processing: - result +[source,ruby] +---- +begin + doc = context.parse(xml_string) +rescue Moxml::ParseError => e + # Handles XML parsing errors + puts "Parse error at line #{e.line}, column #{e.column}" + puts "Message: #{e.message}" +rescue Moxml::ValidationError => e + # Handles XML validation errors + puts "Validation error: #{e.message}" +rescue Moxml::XPathError => e + # Handles XPath expression errors + puts "XPath error: #{e.message}" +rescue Moxml::Error => e + # Handles other Moxml-specific errors + puts "Error: #{e.message}" end ---- -==== Backend-specific issues +== Configuration +Moxml can be configured globally or per instance: + [source,ruby] ---- -# Handle backend limitations -def safe_xpath(doc, xpath) - case Moxml.config.backend - when :nokogiri - doc.xpath(xpath) - when :ox - # Ox has limited XPath support - fallback_xpath_search(doc, xpath) - when :oga - # Handle Oga-specific XPath syntax - modified_xpath = adjust_xpath_for_oga(xpath) - doc.xpath(modified_xpath) - end +# Global configuration +Moxml.configure do |config| + config.default_adapter = :nokogiri + config.strict = true + config.encoding = 'UTF-8' end -# Handle backend switching -def with_backend(backend) - original_backend = Moxml.config.backend - Moxml.config.backend = backend - yield -ensure - Moxml.config.backend = original_backend +# Instance configuration +moxml = Moxml.new do |config| + config.adapter = :ox + config.strict = false end ---- -=== Performance optimization +== Thread safety -==== Document creation +Moxml is thread-safe when used properly. Each instance maintains its own state +and can be used safely in concurrent operations: [source,ruby] ---- -# Efficient document building -def build_large_document - doc = Moxml::Document.new - root = doc.create_element('root') - doc.add_child(root) - - # Pre-allocate elements - elements = Array.new(1000) do |i| - elem = doc.create_element('item') - elem['id'] = i.to_s - elem +class XmlProcessor + def initialize + @mutex = Mutex.new + @context = Moxml.new end - # Batch add elements - elements.each do |elem| - root.add_child(elem) + def process(xml) + @mutex.synchronize do + doc = @context.parse(xml) + # Modify document + doc.to_xml + end end - - doc end +---- -# Memory-efficient processing -def process_large_xml(xml_string) - result = [] - doc = Moxml::Document.parse(xml_string) +== Performance considerations - doc.xpath('//item').each do |item| - # Process and immediately discard - result << process_item(item) - item = nil - end +=== Memory management - doc = nil - GC.start +Moxml maintains a node registry to ensure consistent object mapping: - result -end ----- - -==== Query optimization - [source,ruby] ---- -# Optimize node selection -def efficient_node_selection(doc) - # Cache frequently used nodes - @header_nodes ||= doc.xpath('//header').to_a - - # Use specific selectors - doc.xpath('//specific/path') # Better than '//*[name()="specific"]' - - # Combine queries when possible - doc.xpath('//a | //b') # Better than two separate queries -end - -# Optimize attribute access -def efficient_attribute_handling(element) - # Cache attribute values - @cached_attrs ||= element.attributes - - # Direct attribute access - value = element['attr'] # Better than element.attributes['attr'] - - # Batch attribute updates - attrs = {'id' => '1', 'class' => 'new', 'data' => 'value'} - attrs.each { |k,v| element[k] = v } -end +doc = context.parse(large_xml) +# Process document +doc = nil # Allow garbage collection of document and registry +GC.start # Force garbage collection if needed ---- -==== Serialization optimization +=== Efficient querying +Use specific XPath expressions for better performance: + [source,ruby] ---- -# Efficient output generation -def optimized_serialization(doc) - # Minimal output - compact = doc.to_xml( - indent: 0, - pretty: false, - xml_declaration: false - ) +# More efficient - specific path +doc.xpath('//book/title') - # Balanced formatting - readable = doc.to_xml( - indent: 2, - pretty: true, - xml_declaration: true - ) +# Less efficient - requires full document scan +doc.xpath('//title') - # Stream large documents - File.open('large.xml', 'w') do |file| - doc.write_to(file, indent: 2) - end -end +# Most efficient - direct child access +root.xpath('./title') ---- -=== Debugging tips +== Best practices -==== Inspection helpers +=== Document creation [source,ruby] ---- -# Debug node structure -def inspect_node(node, level = 0) - indent = " " * level - puts "#{indent}#{node.class.name}: #{node.name}" - - if node.respond_to?(:attributes) - node.attributes.each do |name, attr| - puts "#{indent} @#{name}=#{attr.value.inspect}" +# Preferred - using builder pattern +doc = Moxml.new.build do + declaration version: "1.0", encoding: "UTF-8" + element 'root' do + element 'child' do + text 'content' end end - - if node.respond_to?(:children) - node.children.each { |child| inspect_node(child, level + 1) } - end end -# Track node operations -def debug_node_operations - nodes_created = 0 - nodes_removed = 0 - - yield -ensure - puts "Nodes created: #{nodes_created}" - puts "Nodes removed: #{nodes_removed}" -end +# Alternative - direct manipulation +doc = Moxml.new.create_document +doc.add_declaration(version: "1.0", encoding: "UTF-8") +root = doc.create_element('root') +doc.add_child(root) ---- -==== Backend validation +=== Node manipulation [source,ruby] ---- -# Verify backend behavior -def verify_backend_compatibility - doc = Moxml::Document.new +# Preferred - chainable operations +element + .add_namespace('dc', 'http://purl.org/dc/elements/1.1/') + .add_child(doc.create_text('content')) - # Test basic operations - element = doc.create_element('test') - doc.add_child(element) - - # Verify node handling - raise "Node creation failed" unless doc.root - raise "Node type wrong" unless doc.root.is_a?(Moxml::Element) - - # Verify serialization - xml = doc.to_xml - raise "Serialization failed" unless xml.include?('<test/>') - - puts "Backend verification successful" -rescue => e - puts "Backend verification failed: #{e.message}" +# Preferred - clear node type checking +if node.element? + node.add_child(doc.create_text('content')) end ---- -== Error handling - -Moxml provides unified error handling: - -* `Moxml::Error` - Base error class -* `Moxml::ParseError` - XML parsing errors -* `Moxml::ArgumentError` - Invalid argument errors - -=== Error handling patterns - -[source,ruby] ----- -# Handle parsing errors -begin - doc = Moxml::Document.parse(xml_string) -rescue Moxml::ParseError => e - logger.error "Parse error: #{e.message}" - logger.error "At line #{e.line}, column #{e.column}" - raise -end - -# Handle invalid operations -begin - element['invalid/name'] = 'value' -rescue Moxml::ArgumentError => e - logger.warn "Invalid operation: #{e.message}" - # Use alternative approach -end - -# Custom error handling -class XmlProcessor - def process(xml) - doc = Moxml::Document.parse(xml) - yield doc - rescue Moxml::Error => e - handle_moxml_error(e) - rescue StandardError => e - handle_standard_error(e) - ensure - doc = nil - end -end ----- - == Contributing -Bug reports and pull requests are welcome on GitHub at -https://github.com/lutaml/moxml. +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin feature/my-new-feature`) +5. Create a new Pull Request -=== Development guidelines +== License -* Follow Ruby style guide -* Add tests for new features -* Update documentation -* Ensure backwards compatibility -* Consider performance implications -* Test with all supported backends +Copyright (c) 2024 Ribose Inc. -== Copyright and license +This project is licensed under the BSD-2-Clause License. See the LICENSE file for details. -Copyright Ribose. - -The gem is available as open source under the terms of the BSD-2-Clause License.