openstax_cnx 0.1.0 → 0.1.1

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: 1290e2c41b0cfe6e5f18b3a8d2f5f423d1ecec4d661b589929514a790064683d
4
- data.tar.gz: b86557dc8226f2179fcb21990bdc5b6a2c796308943b4aa9eca71a932b258e95
3
+ metadata.gz: 9157ba0a43475c290a7dfb335ed33b03753b6be00a24af0897108ff376471e6c
4
+ data.tar.gz: b679d19ea1a362100847dbd59f409d11bd7fb612ec5a2f4b713b2af7fd9884b2
5
5
  SHA512:
6
- metadata.gz: f704b248e1e7f1710ff6d75af6595171437279131f3f478cd6305ae7797578ce67026b56a8ea37ce4349b63b34023cd1ce9db909d39f51c3725cccf315bdc031
7
- data.tar.gz: 2139b21f10a4157e532ca4e92ed071cb2aa4cc3df0a1d7eff86f43e8033d1d07f542b1b36114b43f9acf7892566bc022514467e3c3b74729844059d13b7b3e14
6
+ metadata.gz: b27fd15c9f37a2b823940825f33b2d9c3c4c0e84fe7991541a60018ec36af69bd0b63c7a42557d5c4970a5d5d9ef27434d9d8fffcbe9ee7110fa4a3c4d8cc53a
7
+ data.tar.gz: 1c7c17740aecc143cd5bd757a2d97dd00172eff2a0c7ec0192da6f08d7269a81788f8a3cfed16527a6edf2f70fe92fc2ec057a8e660f759f44bc8fc014388590
@@ -0,0 +1,21 @@
1
+ name: Tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ tests:
11
+ timeout-minutes: 10
12
+ runs-on: ubuntu-24.04
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: 3.1
19
+ bundler-cache: true
20
+ - name: Test
21
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  .env
14
14
 
15
15
  .byebug_history
16
+
17
+ Gemfile.lock
data/.travis.yml CHANGED
@@ -4,4 +4,6 @@ language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
6
  - 2.5.0
7
+ notifications:
8
+ email: false
7
9
  before_install: gem install bundler -v 1.17.2
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # OpenStax::Cnx
2
2
 
3
+ [![Build Status](https://travis-ci.org/openstax/cnx-ruby.svg?branch=master)](https://travis-ci.org/openstax/cnx-ruby)
4
+
3
5
  This gem provides a Ruby interface to OpenStax book content.
4
6
 
5
7
  ## Installation
@@ -36,7 +38,7 @@ Book
36
38
  etc...
37
39
  ```
38
40
 
39
- Some OpenStax books organized chapters into units. The units are BookParts like the chapters are:
41
+ Some OpenStax books organize chapters into units. The units are BookParts like the chapters are:
40
42
 
41
43
  ```
42
44
  Book
@@ -70,6 +72,12 @@ book.baked # => "2019-03-20T14:24:26.164476-05:00"
70
72
  book.url # => "https://archive.cnx.org/contents/031da8d3-b525-429c-80cf-6c8ed997733a"
71
73
  ```
72
74
 
75
+ To get all pages in a book:
76
+
77
+ ```ruby
78
+ book.root_book_part.pages.count # => 460
79
+ ```
80
+
73
81
  To dig deeper, get the `root_book_part` and then is `parts`
74
82
 
75
83
  ```ruby
@@ -1,7 +1,7 @@
1
1
  module OpenStax::Cnx::V1
2
2
  class Book
3
3
 
4
- def initialize(id: nil, hash: nil, title: nil, tree: nil, root_book_part: nil)
4
+ def initialize(id:, hash: nil, title: nil, tree: nil, root_book_part: nil)
5
5
  @id = id
6
6
  @hash = hash
7
7
  @title = title
@@ -1,14 +1,16 @@
1
1
  module OpenStax::Cnx::V1
2
2
  class BookPart
3
3
 
4
+ CONTENTS = 'contents'
5
+
6
+ attr_reader :hash, :is_root, :book
7
+
4
8
  def initialize(hash: {}, is_root: false, book: nil)
5
9
  @hash = hash
6
10
  @is_root = is_root
7
11
  @book = book
8
12
  end
9
13
 
10
- attr_reader :hash, :is_root, :book
11
-
12
14
  def parsed_title
13
15
  @parsed_title ||= OpenStax::Cnx::V1::Baked.parse_title(
14
16
  hash.fetch('title') { |key| raise "#{self.class.name} id=#{@id} is missing #{key}" }
@@ -24,14 +26,14 @@ module OpenStax::Cnx::V1
24
26
  end
25
27
 
26
28
  def contents
27
- @contents ||= hash.fetch('contents') do |key|
29
+ @contents ||= hash.fetch(CONTENTS) do |key|
28
30
  raise "#{self.class.name} id=#{@id} is missing #{key}"
29
31
  end
30
32
  end
31
33
 
32
34
  def parts
33
35
  @parts ||= contents.map do |hash|
34
- if hash.has_key? 'contents'
36
+ if hash.has_key? CONTENTS
35
37
  self.class.new(hash: hash, book: book)
36
38
  else
37
39
  OpenStax::Cnx::V1::Page.new(hash: hash, book: book)
@@ -44,5 +46,11 @@ module OpenStax::Cnx::V1
44
46
  @is_chapter ||= parts.none? { |part| part.is_a?(self.class) }
45
47
  end
46
48
 
49
+ def pages
50
+ @pages ||= parts.map do |part|
51
+ part.is_a?(Page) ? part : part.pages
52
+ end.flatten
53
+ end
54
+
47
55
  end
48
56
  end
@@ -0,0 +1,9 @@
1
+ module OpenStax::Cnx::V1
2
+ class Element
3
+ attr_reader :node
4
+
5
+ def initialize(node:)
6
+ @node = node
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ module OpenStax::Cnx::V1
2
+ class Figure < Element
3
+ MATCH_FIGURE = "//*[contains(@class, 'os-figure')]"
4
+
5
+ MATCH_FIGURE_CAPTION = ".//*[contains(@class, 'os-caption')]"
6
+ MATCH_FIGURE_ALT_TEXT = './/*[@alt]'
7
+ MATCH_FIGURE_DATA_ALT_TEXT = './/*[@data-alt]'
8
+ MATCH_FIGURE_ELEM = './/figure'
9
+
10
+ def initialize(node:)
11
+ super
12
+ end
13
+
14
+ def caption
15
+ node.at_xpath(MATCH_FIGURE_CAPTION).try(:text)
16
+ end
17
+
18
+ def id
19
+ node.at_xpath(MATCH_FIGURE_ELEM).attr('id')
20
+ end
21
+
22
+ def alt_text
23
+ node.at_xpath(MATCH_FIGURE_ALT_TEXT).try(:attr, 'alt') ||
24
+ node.at_xpath(MATCH_FIGURE_DATA_ALT_TEXT).try(:attr, 'data-alt')
25
+ end
26
+
27
+ def self.matcher
28
+ MATCH_FIGURE
29
+ end
30
+
31
+ def self.matches?(node)
32
+ node.matches?(MATCH_FIGURE)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ module OpenStax::Cnx::V1
2
+ class KeyTerm < Element
3
+ MATCH_KEY_TERM = "//*[contains(@class, 'os-glossary-container')]//dl"
4
+
5
+ MATCH_KEY_TERM_TEXT = ".//dt"
6
+ MATCH_KEY_TERM_DESCRIPTION = ".//dd"
7
+
8
+ def initialize(node:)
9
+ super
10
+ end
11
+
12
+ def description
13
+ node.xpath(MATCH_KEY_TERM_DESCRIPTION).text
14
+ end
15
+
16
+ def id
17
+ node.attr('id')
18
+ end
19
+
20
+ def term
21
+ node.xpath(MATCH_KEY_TERM_TEXT).text
22
+ end
23
+
24
+ def self.matcher
25
+ MATCH_KEY_TERM
26
+ end
27
+
28
+ def self.matches?(node)
29
+ node.matches?(MATCH_KEY_TERM)
30
+ end
31
+ end
32
+ end
@@ -67,6 +67,14 @@ module OpenStax::Cnx::V1
67
67
  @is_intro = title.start_with?('Introduction')
68
68
  end
69
69
 
70
+ def preface?
71
+ content_dom.xpath('boolean(//html/body/div[@data-type="page"][@class="preface"])')
72
+ end
73
+
74
+ def index?
75
+ content_dom.xpath('boolean(//div[contains(@class,"os-index-container")])')
76
+ end
77
+
70
78
  def full_hash
71
79
  @full_hash ||= OpenStax::Cnx::V1.fetch(url)
72
80
  end
@@ -161,8 +169,48 @@ module OpenStax::Cnx::V1
161
169
  @tags.values
162
170
  end
163
171
 
172
+ def remove_elements(xpath:)
173
+ content_dom.xpath(xpath).each do | xpath_element |
174
+ xpath_element.remove
175
+ end
176
+ end
177
+
178
+ # Pass the
179
+ #
180
+ # e.g.,
181
+ # element classes = [
182
+ # OpenStax::Cnx::V1::Figure,
183
+ # OpenStax::Cnx::V1::Paragraph,
184
+ # OpenStax::Cnx::V1::KeyTerm,
185
+ # ]
186
+ #
187
+ # you want to retrieve with the page
188
+ def elements(element_classes:)
189
+ # This join is important to OR together all the xpaths in order to determine
190
+ # the matched element's order inside the page. Xpath does this for us.
191
+ match_all_elements = element_classes.map(&:matcher).join(' | ')
192
+
193
+ working_element_index = []
194
+
195
+ # Match on all the elements. Create Element objects with the matching xpath node.
196
+ content_dom.xpath(match_all_elements).each do | xpath_element |
197
+ element_class = element_classes.detect do | elem_class |
198
+ elem_class.matches?(xpath_element)
199
+ end
200
+
201
+ element = element_class.new(node: xpath_element)
202
+ working_element_index << element if element
203
+ end
204
+
205
+ working_element_index
206
+ end
207
+
164
208
  protected
165
209
 
210
+ def content_dom
211
+ @content_dom ||= Nokogiri::HTML(content)
212
+ end
213
+
166
214
  def url_for(path)
167
215
  book.nil? ? OpenStax::Cnx::V1.archive_url_for(path) : "#{book.canonical_url}:#{path}"
168
216
  end
@@ -0,0 +1,25 @@
1
+ module OpenStax::Cnx::V1
2
+ class Paragraph < Element
3
+ MATCH_PARAGRAPH = '//p[@id]'
4
+
5
+ def initialize(node:)
6
+ super
7
+ end
8
+
9
+ def text
10
+ node.text
11
+ end
12
+
13
+ def id
14
+ node.attr('id')
15
+ end
16
+
17
+ def self.matcher
18
+ MATCH_PARAGRAPH
19
+ end
20
+
21
+ def self.matches?(node)
22
+ node.matches?(MATCH_PARAGRAPH)
23
+ end
24
+ end
25
+ end
@@ -8,6 +8,10 @@ require_relative './v1/http_error'
8
8
 
9
9
  require_relative './v1/book'
10
10
  require_relative './v1/book_part'
11
+ require_relative './v1/element'
12
+ require_relative './v1/figure'
13
+ require_relative './v1/key_term'
14
+ require_relative './v1/paragraph'
11
15
  require_relative './v1/page'
12
16
  require_relative './v1/baked'
13
17
 
@@ -117,7 +121,7 @@ module OpenStax::Cnx::V1
117
121
  def fetch(url)
118
122
  begin
119
123
  OpenStax::Cnx::V1.logger.debug { "Fetching #{url}" }
120
- data = JSON.parse open(url, 'ACCEPT' => 'text/json').read
124
+ data = JSON.parse URI.open(url, 'ACCEPT' => 'text/json').read
121
125
  data.delete("history") if configuration.ignore_history
122
126
  data
123
127
  rescue OpenURI::HTTPError => exception
@@ -125,8 +129,8 @@ module OpenStax::Cnx::V1
125
129
  end
126
130
  end
127
131
 
128
- def book(options = {})
129
- OpenStax::Cnx::V1::Book.new(options)
132
+ def book(**options)
133
+ OpenStax::Cnx::V1::Book.new(**options)
130
134
  end
131
135
 
132
136
  end
@@ -1,5 +1,5 @@
1
1
  module OpenStax
2
2
  module Cnx
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
data/openstax_cnx.gemspec CHANGED
@@ -37,10 +37,11 @@ Gem::Specification.new do |spec|
37
37
  spec.require_paths = ["lib"]
38
38
 
39
39
  spec.add_dependency "nokogiri"
40
+ spec.add_dependency "addressable"
40
41
 
41
- spec.add_development_dependency "bundler", "~> 1.17"
42
- spec.add_development_dependency "rake", "~> 10.0"
43
- spec.add_development_dependency "rspec", "~> 3.0"
42
+ spec.add_development_dependency "bundler"
43
+ spec.add_development_dependency "rake"
44
+ spec.add_development_dependency "rspec"
44
45
  spec.add_development_dependency "dotenv"
45
46
  spec.add_development_dependency "webmock"
46
47
  spec.add_development_dependency "vcr"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstax_cnx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - JP Slavinsky
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-06 00:00:00.000000000 Z
11
+ date: 2025-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -24,48 +24,62 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - "~>"
45
+ - - ">="
32
46
  - !ruby/object:Gem::Version
33
- version: '1.17'
47
+ version: '0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - "~>"
52
+ - - ">="
39
53
  - !ruby/object:Gem::Version
40
- version: '1.17'
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: '10.0'
61
+ version: '0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '10.0'
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - "~>"
73
+ - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: '3.0'
75
+ version: '0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - "~>"
80
+ - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: '3.0'
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: dotenv
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -129,12 +143,12 @@ executables: []
129
143
  extensions: []
130
144
  extra_rdoc_files: []
131
145
  files:
146
+ - ".github/workflows/tests.yml"
132
147
  - ".gitignore"
133
148
  - ".rspec"
134
149
  - ".travis.yml"
135
150
  - CODE_OF_CONDUCT.md
136
151
  - Gemfile
137
- - Gemfile.lock
138
152
  - LICENSE.txt
139
153
  - README.md
140
154
  - Rakefile
@@ -144,8 +158,12 @@ files:
144
158
  - lib/openstax/cnx/v1/baked.rb
145
159
  - lib/openstax/cnx/v1/book.rb
146
160
  - lib/openstax/cnx/v1/book_part.rb
161
+ - lib/openstax/cnx/v1/element.rb
162
+ - lib/openstax/cnx/v1/figure.rb
147
163
  - lib/openstax/cnx/v1/http_error.rb
164
+ - lib/openstax/cnx/v1/key_term.rb
148
165
  - lib/openstax/cnx/v1/page.rb
166
+ - lib/openstax/cnx/v1/paragraph.rb
149
167
  - lib/openstax/cnx/version.rb
150
168
  - lib/openstax_cnx.rb
151
169
  - openstax_cnx.gemspec
@@ -156,7 +174,7 @@ metadata:
156
174
  allowed_push_host: https://rubygems.org
157
175
  homepage_uri: https://github.com/openstax/cnx-ruby
158
176
  source_code_uri: https://github.com/openstax/cnx-ruby
159
- post_install_message:
177
+ post_install_message:
160
178
  rdoc_options: []
161
179
  require_paths:
162
180
  - lib
@@ -171,9 +189,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
189
  - !ruby/object:Gem::Version
172
190
  version: '0'
173
191
  requirements: []
174
- rubyforge_project:
175
- rubygems_version: 2.7.3
176
- signing_key:
192
+ rubygems_version: 3.5.21
193
+ signing_key:
177
194
  specification_version: 4
178
195
  summary: Bindings to fetch and inspect CNX books
179
196
  test_files: []
data/Gemfile.lock DELETED
@@ -1,57 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- openstax_cnx (0.1.0)
5
- nokogiri
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- addressable (2.6.0)
11
- public_suffix (>= 2.0.2, < 4.0)
12
- byebug (11.0.1)
13
- crack (0.4.3)
14
- safe_yaml (~> 1.0.0)
15
- diff-lcs (1.3)
16
- dotenv (2.7.2)
17
- hashdiff (0.3.8)
18
- mini_portile2 (2.4.0)
19
- nokogiri (1.10.2)
20
- mini_portile2 (~> 2.4.0)
21
- public_suffix (3.0.3)
22
- rake (10.5.0)
23
- rspec (3.8.0)
24
- rspec-core (~> 3.8.0)
25
- rspec-expectations (~> 3.8.0)
26
- rspec-mocks (~> 3.8.0)
27
- rspec-core (3.8.0)
28
- rspec-support (~> 3.8.0)
29
- rspec-expectations (3.8.2)
30
- diff-lcs (>= 1.2.0, < 2.0)
31
- rspec-support (~> 3.8.0)
32
- rspec-mocks (3.8.0)
33
- diff-lcs (>= 1.2.0, < 2.0)
34
- rspec-support (~> 3.8.0)
35
- rspec-support (3.8.0)
36
- safe_yaml (1.0.5)
37
- vcr (4.0.0)
38
- webmock (3.5.1)
39
- addressable (>= 2.3.6)
40
- crack (>= 0.3.2)
41
- hashdiff
42
-
43
- PLATFORMS
44
- ruby
45
-
46
- DEPENDENCIES
47
- bundler (~> 1.17)
48
- byebug
49
- dotenv
50
- openstax_cnx!
51
- rake (~> 10.0)
52
- rspec (~> 3.0)
53
- vcr
54
- webmock
55
-
56
- BUNDLED WITH
57
- 1.17.2