From cd7dadad010c686536ea33bf824c8127258a5d1b Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Fri, 1 Nov 2024 15:39:05 +0800 Subject: [PATCH] feat: initial functionality --- .rubocop_todo.yml | 24 + Gemfile | 7 +- README.adoc | 90 ++-- lib/oasis/etm/entry.rb | 2 +- lib/oasis/etm/table.rb | 2 +- lib/oasis/etm/tgroup.rb | 2 +- oasis-etm_wrapped.txt | 434 ------------------ .../fixtures/isosts/isosts_tables.cals.01.xml | 16 + .../fixtures/isosts/isosts_tables.cals.02.xml | 45 ++ .../fixtures/isosts/isosts_tables.cals.03.xml | 35 ++ .../fixtures/isosts/isosts_tables.cals.04.xml | 34 ++ .../fixtures/isosts/isosts_tables.cals.05.xml | 63 +++ .../fixtures/isosts/isosts_tables.cals.06.xml | 52 +++ .../fixtures/isosts/isosts_tables.cals.07.xml | 33 ++ .../fixtures/isosts/isosts_tables.cals.08.xml | 37 ++ .../fixtures/isosts/isosts_tables.cals.09.xml | 37 ++ .../fixtures/isosts/isosts_tables.cals.10.xml | 44 ++ .../fixtures/isosts/isosts_tables.cals.11.xml | 43 ++ .../fixtures/isosts/isosts_tables.cals.12.xml | 22 + spec/fixtures/native/docbook_example.xml | 48 ++ .../niso-jats/niso-jats-table-wrap.xml | 78 ++++ spec/oasis/etm/colspec_spec.rb | 2 +- spec/oasis/etm/entry_spec.rb | 43 +- spec/oasis/etm/row_spec.rb | 2 +- spec/oasis/etm/table_spec.rb | 2 +- spec/oasis/etm/tbody_spec.rb | 2 +- spec/oasis/etm/tgroup_spec.rb | 2 +- spec/oasis/etm/thead_spec.rb | 2 +- spec/oasis/etm_spec.rb | 80 +++- spec/spec_helper.rb | 19 +- .../shared_examples/validation_examples.rb | 8 +- 31 files changed, 794 insertions(+), 516 deletions(-) create mode 100644 .rubocop_todo.yml delete mode 100644 oasis-etm_wrapped.txt create mode 100644 spec/fixtures/isosts/isosts_tables.cals.01.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.02.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.03.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.04.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.05.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.06.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.07.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.08.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.09.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.10.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.11.xml create mode 100644 spec/fixtures/isosts/isosts_tables.cals.12.xml create mode 100644 spec/fixtures/native/docbook_example.xml create mode 100644 spec/fixtures/niso-jats/niso-jats-table-wrap.xml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..5252434 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,24 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2024-11-01 07:12:25 UTC using RuboCop version 1.68.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Exclude: + - 'spec/oasis/etm/entry_spec.rb' + +# Offense count: 6 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 10 + +# Offense count: 12 +RSpec/MultipleExpectations: + Max: 10 diff --git a/Gemfile b/Gemfile index ffb335f..02c2b1e 100644 --- a/Gemfile +++ b/Gemfile @@ -5,8 +5,11 @@ source "https://rubygems.org" # Specify your gem's dependencies in oasis-etm.gemspec gemspec +gem "nokogiri" gem "rake", "~> 13.0" - gem "rspec", "~> 3.0" - gem "rubocop", "~> 1.21" +gem "rubocop-performance" +gem "rubocop-rake" +gem "rubocop-rspec" +gem "xml-c14n" diff --git a/README.adoc b/README.adoc index be84279..c81542c 100644 --- a/README.adoc +++ b/README.adoc @@ -1,42 +1,4 @@ -= OASIS Exchange Table Model library - -https://github.com/lutaml/oasis-etm[image:https://img.shields.io/github/stars/lutaml/oasis-etm.svg?style=social[GitHub Stars]] -https://github.com/lutaml/oasis-etm[image:https://img.shields.io/github/forks/lutaml/oasis-etm.svg?style=social[GitHub Forks]] -image:https://img.shields.io/github/license/lutaml/oasis-etm.svg[License] -image:https://img.shields.io/github/actions/workflow/status/lutaml/oasis-etm/test.yml?branch=main[Build Status] -image:https://img.shields.io/gem/v/oasis-etm.svg[RubyGems Version] - -== Purpose - - -== Features - - -== Installation - -Add this line to your application's Gemfile: - -[source,ruby] ----- -gem 'oasis-etm' ----- - -And then execute: - -[source,shell] ----- -bundle install ----- - -Or install it yourself as: - -[source,shell] ----- -gem install oasis-etm ----- - = OASIS Exchange Table Model (ETM) Parser -:source-highlighter: rouge :toc: macro :toclevels: 3 @@ -48,8 +10,26 @@ toc::[] == Purpose -Ruby library to parse and create OASIS Exchange Table Model (ETM) formatted tables. -This library provides a Ruby implementation of the OASIS Technical Resolution TR 9503:1995. +The OASIS ETM format is a simple XML format for representing tables for +exchange. It is used in the DocBook and NISO JATS standards. + +This library provides a Ruby implementation of the +https://www.oasis-open.org/specs/a503.htm[OASIS Technical Resolution TR 9503:1995]. + +== Features + +* Full implementation of the OASIS Exchange Table Model TR 9503:1995 +* Intentionally excludes CALS table features not part of the Exchange subset (like `tfoot`) +* XML serialization and deserialization +* Validation of attribute values +* Support for all Exchange Table Model elements: +** `table` +** `tgroup` +** `colspec` +** `thead` +** `tbody` +** `row` +** `entry` == Installation @@ -62,22 +42,22 @@ gem 'oasis-etm' And then execute: -[source,sh] +[source,shell] ---- -$ bundle install +bundle install ---- Or install it yourself as: -[source,sh] +[source,shell] ---- -$ gem install oasis-etm +gem install oasis-etm ---- == Usage -=== Basic Example +=== Basic example [source,ruby] ---- @@ -119,7 +99,7 @@ table = Oasis::Etm::Table.new( xml = table.to_xml ---- -=== XML Schema +=== XML schema The OASIS ETM format follows this basic structure: @@ -149,6 +129,24 @@ The OASIS ETM format follows this basic structure: ---- +== Development + +=== Test files + +`spec/fixtures/native/docbook_example.xml`:: +https://tdg.docbook.org/tdg/4.5/table + +`spec/fixtures/niso-jats/niso-jats-table-wrap.xml`:: +https://jats.nlm.nih.gov/options/OASIS/tag-library/19990315/element/oasis-table.html +This is to test the OASIS exchange table model when it is namespaced from +another location. + +`spec/fixtures/isosts/isosts_tables.cals.{nn}.xml`:: +Tables extracted from https://www.iso.org/schema/isosts/cals/test/isosts_tables.cals.xml. +This is to test the OASIS exchange table model when it is namespaced from +another location. + + == Contributing Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct. diff --git a/lib/oasis/etm/entry.rb b/lib/oasis/etm/entry.rb index 83cd9af..a527614 100644 --- a/lib/oasis/etm/entry.rb +++ b/lib/oasis/etm/entry.rb @@ -14,7 +14,7 @@ class Entry < Lutaml::Model::Serializable attribute :valign, :string, values: %w[top middle bottom] # Content - attribute :content, :string + attribute :content, :string, raw: true xml do root "entry" diff --git a/lib/oasis/etm/table.rb b/lib/oasis/etm/table.rb index 5bdcb2b..9ee177b 100644 --- a/lib/oasis/etm/table.rb +++ b/lib/oasis/etm/table.rb @@ -14,7 +14,7 @@ class Table < Lutaml::Model::Serializable attribute :tgroups, Tgroup, collection: true xml do - root "table" + root "table", ordered: true # Frame mappings map_attribute "frame", to: :frame diff --git a/lib/oasis/etm/tgroup.rb b/lib/oasis/etm/tgroup.rb index 1893560..d9a7cde 100644 --- a/lib/oasis/etm/tgroup.rb +++ b/lib/oasis/etm/tgroup.rb @@ -19,7 +19,7 @@ class Tgroup < Lutaml::Model::Serializable attribute :tbody, Tbody xml do - root "tgroup" + root "tgroup", ordered: true # Attribute mappings map_attribute "cols", to: :cols diff --git a/oasis-etm_wrapped.txt b/oasis-etm_wrapped.txt deleted file mode 100644 index cc7a7a2..0000000 --- a/oasis-etm_wrapped.txt +++ /dev/null @@ -1,434 +0,0 @@ ---- START FILE: CODE_OF_CONDUCT.md --- -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations ---- END FILE: CODE_OF_CONDUCT.md --- ---- START FILE: Gemfile --- -# frozen_string_literal: true - -source "https://rubygems.org" - -# Specify your gem's dependencies in oasis-etm.gemspec -gemspec - -gem "rake", "~> 13.0" - -gem "rspec", "~> 3.0" - -gem "rubocop", "~> 1.21" ---- END FILE: Gemfile --- ---- START FILE: README.adoc --- -= OASIS Exchange Table Model library - -https://github.com/lutaml/oasis-etm[image:https://img.shields.io/github/stars/lutaml/oasis-etm.svg?style=social[GitHub Stars]] -https://github.com/lutaml/oasis-etm[image:https://img.shields.io/github/forks/lutaml/oasis-etm.svg?style=social[GitHub Forks]] -image:https://img.shields.io/github/license/lutaml/oasis-etm.svg[License] -image:https://img.shields.io/github/actions/workflow/status/lutaml/oasis-etm/test.yml?branch=main[Build Status] -image:https://img.shields.io/gem/v/oasis-etm.svg[RubyGems Version] - -== Purpose - - -== Features - - -== Installation - -Add this line to your application's Gemfile: - -[source,ruby] ----- -gem 'oasis-etm' ----- - -And then execute: - -[source,shell] ----- -bundle install ----- - -Or install it yourself as: - -[source,shell] ----- -gem install oasis-etm ----- - -= OASIS Exchange Table Model (ETM) Parser -:source-highlighter: rouge -:toc: macro -:toclevels: 3 - -image:https://img.shields.io/gem/v/oasis-etm.svg[Gem Version, link=https://rubygems.org/gems/oasis-etm] -image:https://github.com/yourusername/oasis-etm/actions/workflows/test.yml/badge.svg[Build Status, link=https://github.com/yourusername/oasis-etm/actions/workflows/test.yml] -image:https://img.shields.io/github/license/yourusername/oasis-etm.svg[License, link=https://github.com/yourusername/oasis-etm/blob/main/LICENSE] - -toc::[] - -== Purpose - -Ruby library to parse and create OASIS Exchange Table Model (ETM) formatted tables. -This library provides a Ruby implementation of the OASIS Technical Resolution TR 9503:1995. - -== Installation - -Add this line to your application's Gemfile: - -[source,ruby] ----- -gem 'oasis-etm' ----- - -And then execute: - -[source,sh] ----- -$ bundle install ----- - -Or install it yourself as: - -[source,sh] ----- -$ gem install oasis-etm ----- - - -== Usage - -=== Basic Example - -[source,ruby] ----- -require 'oasis-etm' - -# Parse an ETM XML file -table = Oasis::Etm::Table.from_xml(File.read('table.xml')) - -# Access table attributes -puts table.frame -puts table.colsep -puts table.rowsep - -# Access table content -table.tgroups.each do |tgroup| - tgroup.colspecs.each do |colspec| - puts "Column #{colspec.colnum}: #{colspec.colwidth}" - end -end - -# Create a new table -table = Oasis::Etm::Table.new( - frame: 'all', - colsep: 1, - rowsep: 1, - tgroups: [ - Oasis::Etm::Tgroup.new( - cols: 3, - colspecs: [ - Oasis::Etm::Colspec.new(colnum: 1, colwidth: '1*'), - Oasis::Etm::Colspec.new(colnum: 2, colwidth: '2*'), - Oasis::Etm::Colspec.new(colnum: 3, colwidth: '1*') - ] - ) - ] -) - -# Convert to XML -xml = table.to_xml ----- - -=== XML Schema - -The OASIS ETM format follows this basic structure: - -[source,xml] ----- - - Sample Table - - - - - - - Header 1 - Header 2 - Header 3 - - - - - Cell 1 - Cell 2 - Cell 3 - - - -
----- - -== Contributing - -Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct. - -. Fork it -. Create your feature branch (`git checkout -b my-new-feature`) -. Commit your changes (`git commit -am 'Add some feature'`) -. Push to the branch (`git push origin my-new-feature`) -. Create new Pull Request - - -== License and Copyright - -This project is licensed under the BSD 2-clause License. -See the link:LICENSE.md[] file for details. - -Copyright Ribose. ---- END FILE: README.adoc --- ---- START FILE: Rakefile --- -# 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] ---- END FILE: Rakefile --- ---- START FILE: lib/oasis/etm.rb --- -# frozen_string_literal: true - -require "lutaml/model" -require_relative "etm/version" -require_relative "etm/table" - -module Oasis - module Etm - class Error < StandardError; end - - # Your code goes here... - end -end ---- END FILE: lib/oasis/etm.rb --- ---- START FILE: lib/oasis/etm/version.rb --- -# frozen_string_literal: true - -module Oasis - module Etm - VERSION = "0.1.0" - end -end ---- END FILE: lib/oasis/etm/version.rb --- ---- START FILE: oasis-etm.gemspec --- -# frozen_string_literal: true - -require_relative "lib/oasis/etm/version" - -Gem::Specification.new do |spec| - spec.name = "oasis-etm" - spec.version = Oasis::Etm::VERSION - spec.authors = ["Ribose Inc."] - spec.email = ["open.source@ribose.com"] - - spec.summary = "Library for OASIS Exchange Table Model" - spec.description = <<~DESCRIPTION - Library for manipulation of OASIS Exchange Table Model XML. - DESCRIPTION - - spec.homepage = "https://github.com/lutaml/oasis-etm" - spec.license = "BSD-2-Clause" - - spec.bindir = "exe" - spec.require_paths = ["lib"] - spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0") - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the - # RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject do |f| - f.match(%r{^(test|features)/}) - end - end - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - - spec.add_dependency "lutaml-model" - spec.metadata["rubygems_mfa_required"] = "true" -end ---- END FILE: oasis-etm.gemspec --- ---- START FILE: sig/oasis/etm.rbs --- -module Oasis - module Etm - VERSION: String - # See the writing guide of rbs: https://github.com/ruby/rbs#guides - end -end ---- END FILE: sig/oasis/etm.rbs --- ---- START FILE: spec/oasis/etm_spec.rb --- -# frozen_string_literal: true - -RSpec.describe Oasis::Etm do - it "has a version number" do - expect(Oasis::Etm::VERSION).not_to be nil - end - - it "does something useful" do - expect(false).to eq(true) - end -end ---- END FILE: spec/oasis/etm_spec.rb --- ---- START FILE: spec/spec_helper.rb --- -# spec/spec_helper.rb -require "bundler/setup" -require "oasis-etm" - -# Require all support files -Dir[File.join(__dir__, "support", "**", "*.rb")].sort.each { |f| require f } - -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = ".rspec_status" - - # Disable RSpec exposing methods globally on `Module` and `main` - config.disable_monkey_patching! - - config.expect_with :rspec do |c| - c.syntax = :expect - end -end ---- END FILE: spec/spec_helper.rb --- diff --git a/spec/fixtures/isosts/isosts_tables.cals.01.xml b/spec/fixtures/isosts/isosts_tables.cals.01.xml new file mode 100644 index 0000000..f3391e0 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.01.xml @@ -0,0 +1,16 @@ + + + + + + + table cell text + table cell text + + + table cell text + table cell text + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.02.xml b/spec/fixtures/isosts/isosts_tables.cals.02.xml new file mode 100644 index 0000000..5609c0f --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.02.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + header text + + + header text + + + header text + + + header text + + + header text + + + + + + table cell text + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + table cell text + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.03.xml b/spec/fixtures/isosts/isosts_tables.cals.03.xml new file mode 100644 index 0000000..bb04afa --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.03.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + header text + header text + + + header text + header text + header text + + + + + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.04.xml b/spec/fixtures/isosts/isosts_tables.cals.04.xml new file mode 100644 index 0000000..dcf85fc --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.04.xml @@ -0,0 +1,34 @@ + + + + + + + + + header text + header text + header text + header text + + + + + + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + + + table footer + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.05.xml b/spec/fixtures/isosts/isosts_tables.cals.05.xml new file mode 100644 index 0000000..779d725 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.05.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + header text + header text + header text + header text + header text + + + + + table cell text + table cell text + table cell text + table cell text + table cell text + + + table cell text + + + table cell text + table cell text + + + table cell text + + + table cell text + table cell text + + + table cell text + table cell text + + + table cell text + table cell text + + + table cell text + + + table cell text + table cell text + + + table cell text + table cell text + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.06.xml b/spec/fixtures/isosts/isosts_tables.cals.06.xml new file mode 100644 index 0000000..e92e139 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.06.xml @@ -0,0 +1,52 @@ + + + + + + + + + + header text + header text + + + header text + header text + header text + + + + + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + + + table cell text + table cell text + table cell text + table cell text + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.07.xml b/spec/fixtures/isosts/isosts_tables.cals.07.xml new file mode 100644 index 0000000..6533eb6 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.07.xml @@ -0,0 +1,33 @@ + + + + + + + + + table default + table default + table default + table default + + + + + rowsep=no + table default + rowsep=yes + + + colsep=yes, rowsep=yes + table default + + + table default + colsep=yes + table default + table default + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.08.xml b/spec/fixtures/isosts/isosts_tables.cals.08.xml new file mode 100644 index 0000000..3793162 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.08.xml @@ -0,0 +1,37 @@ + + + + + + + + + rowsep=yes + rowsep=yes + rowsep=yes + rowsep=yes + + + + + + rowsep=yes + rowsep=yes + rowsep=yes + rowsep=yes + + + rowsep=yes + rowsep=yes + rowsep=yes + rowsep=yes + + + rowsep=yes + rowsep=yes + rowsep=yes + rowsep=yes + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.09.xml b/spec/fixtures/isosts/isosts_tables.cals.09.xml new file mode 100644 index 0000000..9ed2800 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.09.xml @@ -0,0 +1,37 @@ + + + + + + + + + colsep=yes + colsep=yes + colsep=yes + colsep=yes + + + + + + colsep=yes + colsep=yes + colsep=yes + colsep=yes + + + colsep=yes + colsep=yes + colsep=yes + colsep=yes + + + colsep=yes + colsep=yes + colsep=yes + colsep=yes + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.10.xml b/spec/fixtures/isosts/isosts_tables.cals.10.xml new file mode 100644 index 0000000..680d16e --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.10.xml @@ -0,0 +1,44 @@ + + + + + + + + + + table default + table default + table default + table default + + + rowsep=yes + rowsep=yes + rowsep=yes + rowsep=yes + + + + + + table default + table default + table default + table default + + + rowsep=yes; because its the last tbody row before the tfoot + rowsep=yes + rowsep=yes + rowsep=yes + + + table default + table default + table default + table default + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.11.xml b/spec/fixtures/isosts/isosts_tables.cals.11.xml new file mode 100644 index 0000000..74fba67 --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.11.xml @@ -0,0 +1,43 @@ + + + + + + + + + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + + + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + + + + + + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + + + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + + + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + colsep=yes, rowsep=yes + + + + diff --git a/spec/fixtures/isosts/isosts_tables.cals.12.xml b/spec/fixtures/isosts/isosts_tables.cals.12.xml new file mode 100644 index 0000000..776aece --- /dev/null +++ b/spec/fixtures/isosts/isosts_tables.cals.12.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + just lots of text to see the vertical + alignment, just lots of text to see the vertical alignment, just lots of text to see the vertical alignment, just lots of text to see the vertical + alignment + align="center" valign="middle" + align="left" valign="top" + align="right" valign="bottom" + align="char" + align="justify" valign="bottom" + + + + \ No newline at end of file diff --git a/spec/fixtures/native/docbook_example.xml b/spec/fixtures/native/docbook_example.xml new file mode 100644 index 0000000..d39369a --- /dev/null +++ b/spec/fixtures/native/docbook_example.xml @@ -0,0 +1,48 @@ + + +Sample Table + + + + + + + + Horizontal Span + a3 + a4 + a5 + + + + + + b1 + b2 + b3 + b4 + + Vertical Span + + + c1 + Span Both + c4 + + + d1 + d4 + d5 + + + +
\ No newline at end of file diff --git a/spec/fixtures/niso-jats/niso-jats-table-wrap.xml b/spec/fixtures/niso-jats/niso-jats-table-wrap.xml new file mode 100644 index 0000000..b82b4e4 --- /dev/null +++ b/spec/fixtures/niso-jats/niso-jats-table-wrap.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + +Institutional care + + Bed use (days) + + + + + + +Control group +Day hospital +Control +Odds ratio (95% CI) + +Day hospital +Control + + + + + + + +Comprehensive care (5 trials) +151/597 +159/584 +0.91 (0.70 to 1.19) + +20.5 +21.4 + + +Domiciliary care (4 trials) +20/216 + 19/227 +1.61 (0.30 to 8.55) + + 7.7 +11.1 + + +No comprehensive care (3 trials) +37/411 + 66/403 +0.50 (0.26 to 0.96) + +11.2 +11.7 + + +Total +208/1224 +244/1214 +0.77 (0.52 to 1.13) + +15.0 +16.4 + + + + diff --git a/spec/oasis/etm/colspec_spec.rb b/spec/oasis/etm/colspec_spec.rb index 76297ec..c7f1505 100644 --- a/spec/oasis/etm/colspec_spec.rb +++ b/spec/oasis/etm/colspec_spec.rb @@ -43,7 +43,7 @@ end it "generates valid XML" do - expect(colspec.to_xml).to be_equivalent_to(xml) + expect(colspec.to_xml).to be_analogous_with(xml) end end end diff --git a/spec/oasis/etm/entry_spec.rb b/spec/oasis/etm/entry_spec.rb index 62d73e5..84e2a65 100644 --- a/spec/oasis/etm/entry_spec.rb +++ b/spec/oasis/etm/entry_spec.rb @@ -1,3 +1,4 @@ +# spec/oasis/etm/entry_spec.rb RSpec.describe Oasis::Etm::Entry do let(:xml) do <<~XML @@ -54,33 +55,49 @@ end it "generates valid XML" do - expect(entry.to_xml).to be_equivalent_to(xml) + expect(entry.to_xml).to be_analogous_with(xml) end end context "with validation" do it "validates align values" do - expect { - described_class.new(align: "invalid") - }.to raise_error(Lutaml::Model::ValidationError) + entry = described_class.new(align: "invalid") + errors = entry.validate + expect(errors).to match_array( + have_attributes( + message: /align is `invalid`, must be one of the following \[left, right, center, justify, char\]/, + ), + ) end it "validates valign values" do - expect { - described_class.new(valign: "invalid") - }.to raise_error(Lutaml::Model::ValidationError) + entry = described_class.new(valign: "invalid") + errors = entry.validate + expect(errors).to match_array( + have_attributes( + message: /valign is `invalid`, must be one of the following \[top, middle, bottom\]/, + ), + ) end it "validates colsep values" do - expect { - described_class.new(colsep: 2) - }.to raise_error(Lutaml::Model::ValidationError) + entry = described_class.new(colsep: 2) + errors = entry.validate + expect(errors).to match_array( + have_attributes( + message: /colsep is `2`, must be one of the following \[0, 1\]/, + ), + ) end it "validates rowsep values" do - expect { - described_class.new(rowsep: 2) - }.to raise_error(Lutaml::Model::ValidationError) + entry = described_class.new(rowsep: 2) + errors = entry.validate + expect(errors).to match_array( + have_attributes( + message: /rowsep is `2`, must be one of the following \[0, 1\]/, + ), + ) end end end diff --git a/spec/oasis/etm/row_spec.rb b/spec/oasis/etm/row_spec.rb index caf0895..e0c1d2d 100644 --- a/spec/oasis/etm/row_spec.rb +++ b/spec/oasis/etm/row_spec.rb @@ -37,7 +37,7 @@ end it "generates valid XML" do - expect(row.to_xml).to be_equivalent_to(xml) + expect(row.to_xml).to be_analogous_with(xml) end end end diff --git a/spec/oasis/etm/table_spec.rb b/spec/oasis/etm/table_spec.rb index 758c1a8..38177d0 100644 --- a/spec/oasis/etm/table_spec.rb +++ b/spec/oasis/etm/table_spec.rb @@ -88,7 +88,7 @@ end it "generates valid XML" do - expect(table.to_xml).to be_equivalent_to(xml) + expect(table.to_xml).to be_analogous_with(xml) end end end diff --git a/spec/oasis/etm/tbody_spec.rb b/spec/oasis/etm/tbody_spec.rb index dcfff3c..7a1634e 100644 --- a/spec/oasis/etm/tbody_spec.rb +++ b/spec/oasis/etm/tbody_spec.rb @@ -42,7 +42,7 @@ end it "generates valid XML" do - expect(tbody.to_xml).to be_equivalent_to(xml) + expect(tbody.to_xml).to be_analogous_with(xml) end end end diff --git a/spec/oasis/etm/tgroup_spec.rb b/spec/oasis/etm/tgroup_spec.rb index 04866ab..4843e89 100644 --- a/spec/oasis/etm/tgroup_spec.rb +++ b/spec/oasis/etm/tgroup_spec.rb @@ -91,7 +91,7 @@ end it "generates valid XML" do - expect(tgroup.to_xml).to be_equivalent_to(xml) + expect(tgroup.to_xml).to be_analogous_with(xml) end end end diff --git a/spec/oasis/etm/thead_spec.rb b/spec/oasis/etm/thead_spec.rb index 7da3f04..4d1ff11 100644 --- a/spec/oasis/etm/thead_spec.rb +++ b/spec/oasis/etm/thead_spec.rb @@ -42,7 +42,7 @@ end it "generates valid XML" do - expect(thead.to_xml).to be_equivalent_to(xml) + expect(thead.to_xml).to be_analogous_with(xml) end end end diff --git a/spec/oasis/etm_spec.rb b/spec/oasis/etm_spec.rb index d442661..39266c7 100644 --- a/spec/oasis/etm_spec.rb +++ b/spec/oasis/etm_spec.rb @@ -1,11 +1,81 @@ # frozen_string_literal: true +require "pathname" + RSpec.describe Oasis::Etm do - it "has a version number" do - expect(Oasis::Etm::VERSION).not_to be nil - end + fixtures_dir = Pathname.new(__dir__).join("../fixtures") + + describe "XML round-trip conversion" do + describe "native XML" do + xml_files = Dir[fixtures_dir.join("native", "*.xml")] + + xml_files.each do |file_path| + context "with file #{Pathname.new(file_path).relative_path_from(fixtures_dir)}" do + let(:xml_string) { File.read(file_path) } + + it "performs lossless round-trip conversion" do + parsed = Oasis::Etm::Table.from_xml(xml_string) + generated = parsed.to_xml( + pretty: true, + declaration: true, + encoding: "utf-8", + ) + + cleaned_xml_string = xml_string + .gsub(/^<\?xml.*\n/, "") + + expect(generated).to be_analogous_with(cleaned_xml_string) + end + end + end + end + + describe "namespaced XML (ISOSTS)" do + xml_files = Dir[fixtures_dir.join("isosts", "*.xml")] + + xml_files.each do |file_path| + context "with file #{Pathname.new(file_path).relative_path_from(fixtures_dir)}" do + let(:xml_string) { File.read(file_path) } + + it "performs lossless round-trip conversion" do + parsed = Oasis::Etm::Table.from_xml(xml_string) + generated = parsed.to_xml( + pretty: true, + declaration: true, + encoding: "utf-8", + ) + + cleaned_xml_string = xml_string + .gsub(/^<\?xml.*\n/, "") + + expect(generated).to be_analogous_with(cleaned_xml_string) + end + end + end + end + + describe "namespaced XML (NISO JATS)" do + xml_files = Dir[fixtures_dir.join("niso-jats", "*.xml")] + + xml_files.each do |file_path| + context "with file #{Pathname.new(file_path).relative_path_from(fixtures_dir)}" do + let(:xml_string) { File.read(file_path) } + + it "performs lossless round-trip conversion" do + parsed = Oasis::Etm::Table.from_xml(xml_string) + generated = parsed.to_xml( + pretty: true, + declaration: true, + encoding: "utf-8", + ) + + cleaned_xml_string = xml_string + .gsub(/^<\?xml.*\n/, "") - it "does something useful" do - expect(false).to eq(true) + expect(generated).to be_analogous_with(cleaned_xml_string) + end + end + end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 14a2ed3..2d7874d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,11 @@ -# spec/spec_helper.rb -require "bundler/setup" +# frozen_string_literal: true + require "oasis-etm" +require "nokogiri" +require "xml-c14n" # Require all support files -Dir[File.join(__dir__, "support", "**", "*.rb")].sort.each { |f| require f } +Dir[File.join(__dir__, "support", "**", "*.rb")].each { |f| require f } RSpec.configure do |config| # Enable flags like --only-failures and --next-failure @@ -16,3 +18,14 @@ c.syntax = :expect end end + +require "lutaml/model" +require "lutaml/model/xml_adapter/nokogiri_adapter" +require "lutaml/model/json_adapter/standard_json_adapter" +require "lutaml/model/yaml_adapter/standard_yaml_adapter" + +Lutaml::Model::Config.configure do |config| + config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter + config.json_adapter = Lutaml::Model::JsonAdapter::StandardJsonAdapter + config.yaml_adapter = Lutaml::Model::YamlAdapter::StandardYamlAdapter +end diff --git a/spec/support/shared_examples/validation_examples.rb b/spec/support/shared_examples/validation_examples.rb index 64e0cf3..18ebfb7 100644 --- a/spec/support/shared_examples/validation_examples.rb +++ b/spec/support/shared_examples/validation_examples.rb @@ -4,16 +4,16 @@ context "with #{attribute}" do it "accepts valid values" do valid_values.each do |value| - expect { + expect do described_class.new(attribute => value) - }.not_to raise_error + end.not_to raise_error end end it "rejects invalid values" do - expect { + expect do described_class.new(attribute => "invalid") - }.to raise_error(Lutaml::Model::ValidationError) + end.to raise_error(Lutaml::Model::ValidationError) end end end