blacklight_iiif_search 0.0.1.pre.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +46 -0
- data/app/controllers/concerns/blacklight_iiif_search/controller.rb +48 -0
- data/app/models/blacklight_iiif_search/iiif_search.rb +36 -0
- data/app/models/blacklight_iiif_search/iiif_search_annotation.rb +43 -0
- data/app/models/blacklight_iiif_search/iiif_search_response.rb +103 -0
- data/app/models/blacklight_iiif_search/iiif_suggest_response.rb +56 -0
- data/app/models/blacklight_iiif_search/iiif_suggest_search.rb +37 -0
- data/app/models/concerns/blacklight_iiif_search/annotation_behavior.rb +28 -0
- data/app/models/concerns/blacklight_iiif_search/ignored.rb +13 -0
- data/app/models/concerns/blacklight_iiif_search/search_behavior.rb +15 -0
- data/lib/blacklight_iiif_search.rb +14 -0
- data/lib/blacklight_iiif_search/engine.rb +14 -0
- data/lib/blacklight_iiif_search/routes.rb +12 -0
- data/lib/blacklight_iiif_search/version.rb +3 -0
- data/lib/generators/blacklight_iiif_search/controller_generator.rb +37 -0
- data/lib/generators/blacklight_iiif_search/install_generator.rb +52 -0
- data/lib/generators/blacklight_iiif_search/model_generator.rb +17 -0
- data/lib/generators/blacklight_iiif_search/routes_generator.rb +26 -0
- data/lib/generators/blacklight_iiif_search/solr_generator.rb +87 -0
- data/lib/generators/blacklight_iiif_search/templates/iiif_search_builder.rb +15 -0
- data/lib/generators/blacklight_iiif_search/templates/solr/lib/solr-tokenizing_suggester-7.x.jar +0 -0
- data/lib/railties/blacklight_iiif_search.rake +15 -0
- data/spec/controllers/catalog_controller_spec.rb +99 -0
- data/spec/fixtures/sample_solr_documents.yml +272 -0
- data/spec/iiif_search_shared.rb +49 -0
- data/spec/models/blacklight_iiif_search/iiif_search_annotation_spec.rb +36 -0
- data/spec/models/blacklight_iiif_search/iiif_search_response_spec.rb +73 -0
- data/spec/models/blacklight_iiif_search/iiif_search_spec.rb +23 -0
- data/spec/models/blacklight_iiif_search/iiif_suggest_response_spec.rb +43 -0
- data/spec/models/blacklight_iiif_search/iiif_suggest_search_spec.rb +36 -0
- data/spec/models/concerns/blacklight_iiif_search/annotation_behavior_spec.rb +31 -0
- data/spec/models/concerns/blacklight_iiif_search/ignored_spec.rb +10 -0
- data/spec/models/concerns/blacklight_iiif_search/search_behavior_spec.rb +12 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +13 -0
- metadata +238 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# BlacklightIiifSearch
|
2
|
+
module BlacklightIiifSearch
|
3
|
+
require 'blacklight_iiif_search/version'
|
4
|
+
require 'blacklight_iiif_search/engine'
|
5
|
+
require 'iiif/presentation'
|
6
|
+
require 'blacklight'
|
7
|
+
|
8
|
+
autoload :Routes, 'blacklight_iiif_search/routes'
|
9
|
+
|
10
|
+
# returns the full path the the blacklight plugin installation
|
11
|
+
def self.root
|
12
|
+
@root ||= File.expand_path(File.dirname(File.dirname(__FILE__)))
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module BlacklightIiifSearch
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace BlacklightIiifSearch
|
4
|
+
|
5
|
+
# Load rake tasks.
|
6
|
+
rake_tasks do
|
7
|
+
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..'))) do
|
8
|
+
Dir.glob(File.join('railties', '*.rake')).each do |railtie|
|
9
|
+
load railtie
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module BlacklightIiifSearch
|
2
|
+
class Routes
|
3
|
+
def initialize(defaults = {})
|
4
|
+
@defaults = defaults
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(mapper, _options = {})
|
8
|
+
mapper.get 'iiif_search', action: 'iiif_search'
|
9
|
+
mapper.get 'iiif_suggest', action: 'iiif_suggest'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# adds controller-scope behavior to the implementing application
|
2
|
+
require 'rails/generators'
|
3
|
+
|
4
|
+
module BlacklightIiifSearch
|
5
|
+
class ControllerGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
argument :controller_name, type: :string, default: 'catalog'
|
9
|
+
|
10
|
+
desc "
|
11
|
+
This generator makes the following changes to your app's CatalogController:
|
12
|
+
1. Includes BlacklightIiifSearch::Controller
|
13
|
+
2. Adds some basic configuration settings in the configure_blacklight block
|
14
|
+
"
|
15
|
+
|
16
|
+
# Update the blacklight catalog controller
|
17
|
+
def inject_catalog_controller_behavior
|
18
|
+
return if IO.read("app/controllers/#{controller_name}_controller.rb").include?('BlacklightIiifSearch')
|
19
|
+
marker = 'include Blacklight::Catalog'
|
20
|
+
insert_into_file "app/controllers/#{controller_name}_controller.rb", after: marker do
|
21
|
+
"\n\n # CatalogController-scope behavior and configuration for BlacklightIiifSearch
|
22
|
+
include BlacklightIiifSearch::Controller"
|
23
|
+
end
|
24
|
+
marker = 'configure_blacklight do |config|'
|
25
|
+
insert_into_file "app/controllers/#{controller_name}_controller.rb", after: marker do
|
26
|
+
"\n\n # configuration for Blacklight IIIF Content Search
|
27
|
+
config.iiif_search = {
|
28
|
+
full_text_field: 'text',
|
29
|
+
object_relation_field: 'is_page_of_s',
|
30
|
+
supported_params: %w[q page],
|
31
|
+
autocomplete_handler: 'iiif_suggest',
|
32
|
+
suggester_name: 'iiifSuggester'
|
33
|
+
}\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# install BlacklightIiifSearch behavior into implementing application
|
2
|
+
require 'rails/generators'
|
3
|
+
|
4
|
+
module BlacklightIiifSearch
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
argument :search_builder_name, type: :string, default: 'search_builder'
|
9
|
+
argument :controller_name, type: :string, default: 'catalog'
|
10
|
+
|
11
|
+
class_option :'skip-solr', type: :boolean,
|
12
|
+
default: false, desc: 'Skip generating Solr configurations.'
|
13
|
+
|
14
|
+
desc <<-EOS
|
15
|
+
Install generator for Blacklight IIIF Search
|
16
|
+
This generator makes the following changes to your application:
|
17
|
+
1. Injects behavior into CatalogController
|
18
|
+
2. Adds a SearchBuilder to ./app/models
|
19
|
+
3. Adds BlacklightIiifSearch routes to ./config/routes.rb
|
20
|
+
4. Modifies solrconfig.xml to support contextual autocomplete functionality
|
21
|
+
Thanks for installing Blacklight IIIF Search!
|
22
|
+
EOS
|
23
|
+
|
24
|
+
def verify_blacklight_installed
|
25
|
+
return if IO.read('app/controllers/application_controller.rb').include?('include Blacklight::Controller')
|
26
|
+
say_status('info', 'BLACKLIGHT NOT INSTALLED; GENERATING BLACKLIGHT', :blue)
|
27
|
+
generate 'blacklight:install'
|
28
|
+
end
|
29
|
+
|
30
|
+
def insert_to_controllers
|
31
|
+
generate 'blacklight_iiif_search:controller', controller_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def insert_to_models
|
35
|
+
generate 'blacklight_iiif_search:model', search_builder_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def insert_to_routes
|
39
|
+
generate 'blacklight_iiif_search:routes'
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_solr_config
|
43
|
+
generate 'blacklight_iiif_search:solr' unless options[:'skip-solr']
|
44
|
+
end
|
45
|
+
|
46
|
+
def bundle_install
|
47
|
+
Bundler.with_clean_env do
|
48
|
+
run 'bundle install'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# add the SearchBuilder to the implementing application
|
2
|
+
require 'rails/generators'
|
3
|
+
|
4
|
+
module BlacklightIiifSearch
|
5
|
+
class ModelGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
argument :search_builder_model, type: :string, default: 'search_builder'
|
9
|
+
|
10
|
+
desc 'This generator makes the following changes to your app:
|
11
|
+
1. Adds iiif_search_builder.rb to app/models'
|
12
|
+
|
13
|
+
def inject_search_builder_behavior
|
14
|
+
copy_file 'iiif_search_builder.rb', 'app/models/iiif_search_builder.rb'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# insert routing for IIIF Content Search
|
2
|
+
require 'rails/generators'
|
3
|
+
|
4
|
+
module BlacklightIiifSearch
|
5
|
+
class RoutesGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
desc 'This generator makes the following changes to your app:
|
9
|
+
1. Injects route declarations into your routes.rb'
|
10
|
+
|
11
|
+
# Add CommonwealthVlrEngine to the routes
|
12
|
+
def inject_iiif_search_routes
|
13
|
+
unless IO.read('config/routes.rb').include?('BlacklightIiifSearch::Routes')
|
14
|
+
marker = 'Rails.application.routes.draw do'
|
15
|
+
insert_into_file 'config/routes.rb', after: marker do
|
16
|
+
"\n\n concern :iiif_search, BlacklightIiifSearch::Routes.new"
|
17
|
+
end
|
18
|
+
# for blacklight_range_limit
|
19
|
+
bl_routes_marker = /resources :solr_documents[\S\s]*controller: 'catalog' do[\s]*concerns :exportable.*$/
|
20
|
+
inject_into_file 'config/routes.rb', after: bl_routes_marker do
|
21
|
+
"\n concerns :iiif_search"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# adds iiif_suggest configuration to Solr solrconfig.xml and schema.xml
|
2
|
+
# to allow for suggestions limited by a contextField
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
module BlacklightIiifSearch
|
6
|
+
class SolrGenerator < Rails::Generators::Base
|
7
|
+
source_root ::File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
desc 'Adds iiif_suggest configuration to Solr solrconfig.xml and schema.xml'
|
10
|
+
|
11
|
+
def inject_iiif_suggest_solrconfig
|
12
|
+
unless IO.read('solr/conf/solrconfig.xml').include?('iiif_suggest')
|
13
|
+
marker = '</config>'
|
14
|
+
insert_into_file 'solr/conf/solrconfig.xml', before: marker do
|
15
|
+
" <!-- BEGIN Blacklight IIIF Search autocomplete config -->
|
16
|
+
<!-- solr-tokenizing_suggester is necessary to return single terms from the suggester -->
|
17
|
+
<lib dir=\"${solr.install.dir:../../../..}/contrib\" regex=\"solr-tokenizing_suggester-7.x.jar\" />\n
|
18
|
+
<searchComponent name=\"iiif_suggest\" class=\"solr.SuggestComponent\">
|
19
|
+
<lst name=\"suggester\">
|
20
|
+
<str name=\"name\">iiifSuggester</str>
|
21
|
+
<str name=\"lookupImpl\">edu.stanford.dlss.search.suggest.analyzing.TokenizingLookupFactory</str>
|
22
|
+
<str name=\"dictionaryImpl\">DocumentDictionaryFactory</str>
|
23
|
+
<str name=\"suggestAnalyzerFieldType\">textSuggest</str>
|
24
|
+
<str name=\"suggestTokenizingAnalyzerFieldType\">textSuggestTokenizer</str>
|
25
|
+
<str name=\"contextField\">is_page_of_s</str>
|
26
|
+
<str name=\"buildOnCommit\">true</str>
|
27
|
+
<str name=\"field\">iiif_suggest</str>
|
28
|
+
</lst>
|
29
|
+
</searchComponent>\n
|
30
|
+
<requestHandler name=\"/iiif_suggest\" class=\"solr.SearchHandler\" startup=\"lazy\">
|
31
|
+
<lst name=\"defaults\">
|
32
|
+
<str name=\"suggest\">true</str>
|
33
|
+
<str name=\"suggest.count\">5</str>
|
34
|
+
<str name=\"suggest.dictionary\">iiifSuggester</str>
|
35
|
+
</lst>
|
36
|
+
<arr name=\"components\">
|
37
|
+
<str>iiif_suggest</str>
|
38
|
+
</arr>
|
39
|
+
</requestHandler>
|
40
|
+
<!-- END Blacklight IIIF Search autocomplete config -->\n\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def inject_iiif_suggest_schema
|
46
|
+
filepath = 'solr/conf/schema.xml'
|
47
|
+
unless IO.read(filepath).include?('iiif_suggest')
|
48
|
+
field_type_marker = '</types>'
|
49
|
+
insert_into_file filepath, before: field_type_marker do
|
50
|
+
"\n <!-- BEGIN Blacklight IIIF Search autocomplete config -->
|
51
|
+
<fieldType name=\"textSuggestTokenizer\" class=\"solr.TextField\" positionIncrementGap=\"100\">
|
52
|
+
<analyzer>
|
53
|
+
<tokenizer class=\"solr.WhitespaceTokenizerFactory\"/>
|
54
|
+
<filter class=\"solr.StopFilterFactory\" ignoreCase=\"true\" words=\"stopwords_en.txt\"/>
|
55
|
+
<filter class=\"solr.WordDelimiterGraphFilterFactory\"/>
|
56
|
+
<filter class=\"solr.LowerCaseFilterFactory\"/>
|
57
|
+
<filter class=\"solr.HyphenatedWordsFilterFactory\"/>
|
58
|
+
<filter class=\"solr.RemoveDuplicatesTokenFilterFactory\"/>
|
59
|
+
<!-- uncomment below to enable multi-word matches -->
|
60
|
+
<!-- <filter class=\"solr.ShingleFilterFactory\" outputUnigrams=\"true\" outputUnigramsIfNoShingles=\"true\" maxShingleSize=\"3\" /> -->
|
61
|
+
</analyzer>
|
62
|
+
</fieldType>
|
63
|
+
<!-- END Blacklight IIIF Search autocomplete config -->\n\n"
|
64
|
+
end
|
65
|
+
|
66
|
+
fields_marker = '</fields>'
|
67
|
+
insert_into_file filepath, before: fields_marker do
|
68
|
+
" <!-- BEGIN Blacklight IIIF Search autocomplete config -->
|
69
|
+
<field name=\"iiif_suggest\" type=\"textSuggest\" indexed=\"true\" stored=\"true\" multiValued=\"true\" />
|
70
|
+
<!-- END Blacklight IIIF Search autocomplete config -->\n\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
copy_marker = '</schema>'
|
74
|
+
insert_into_file filepath, before: copy_marker do
|
75
|
+
" <!-- BEGIN Blacklight IIIF Search autocomplete config -->
|
76
|
+
<copyField source=\"text\" dest=\"iiif_suggest\"/>
|
77
|
+
<!-- END Blacklight IIIF Search autocomplete config -->\n\n"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def copy_suggester_library
|
83
|
+
copy_file 'solr/lib/solr-tokenizing_suggester-7.x.jar',
|
84
|
+
'solr/lib/solr-tokenizing_suggester-7.x.jar'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# SearchBuilder for full-text searches with highlighting and snippets
|
2
|
+
class IiifSearchBuilder < Blacklight::SearchBuilder
|
3
|
+
include Blacklight::Solr::SearchBuilderBehavior
|
4
|
+
|
5
|
+
self.default_processor_chain += [:ocr_search_params]
|
6
|
+
|
7
|
+
# set params for ocr field searching
|
8
|
+
def ocr_search_params(solr_parameters = {})
|
9
|
+
solr_parameters[:facet] = false
|
10
|
+
solr_parameters[:hl] = true
|
11
|
+
solr_parameters[:'hl.fl'] = blacklight_config.iiif_search[:full_text_field]
|
12
|
+
solr_parameters[:'hl.fragsize'] = 100
|
13
|
+
solr_parameters[:'hl.snippets'] = 10
|
14
|
+
end
|
15
|
+
end
|
data/lib/generators/blacklight_iiif_search/templates/solr/lib/solr-tokenizing_suggester-7.x.jar
ADDED
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :blacklight_iiif_search do
|
2
|
+
namespace :index do
|
3
|
+
desc 'Put sample data into solr'
|
4
|
+
task seed: [:environment] do
|
5
|
+
require 'yaml'
|
6
|
+
docs = YAML.safe_load(File.open(File.join(BlacklightIiifSearch.root,
|
7
|
+
'spec',
|
8
|
+
'fixtures',
|
9
|
+
'sample_solr_documents.yml')))
|
10
|
+
conn = Blacklight.default_index.connection
|
11
|
+
conn.add docs
|
12
|
+
conn.commit
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'iiif_search_shared'
|
2
|
+
RSpec.describe CatalogController do
|
3
|
+
include_context 'iiif_search_shared'
|
4
|
+
|
5
|
+
render_views
|
6
|
+
|
7
|
+
describe 'controller methods' do
|
8
|
+
describe '#iiif_search' do
|
9
|
+
it 'responds to iiif_search' do
|
10
|
+
expect(controller).to respond_to :iiif_search
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#iiif_suggest' do
|
15
|
+
it 'responds to iiif_suggest' do
|
16
|
+
expect(controller).to respond_to :iiif_suggest
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#iiif_search_config' do
|
21
|
+
subject { controller.iiif_search_config }
|
22
|
+
it 'returns the iiif_search config' do
|
23
|
+
expect(subject[:full_text_field]).to_not be_falsey
|
24
|
+
expect(subject[:object_relation_field]).to_not be_falsey
|
25
|
+
expect(subject[:supported_params]).to_not be_empty
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#iiif_search_params' do
|
30
|
+
before do
|
31
|
+
controller.params = { q: query_term, foo: 'bar' }
|
32
|
+
end
|
33
|
+
|
34
|
+
subject { controller.iiif_search_params }
|
35
|
+
it 'only returns the permitted params' do
|
36
|
+
expect(subject.include?(:q)).to be_truthy
|
37
|
+
expect(subject.include?(:foo)).to be_falsey
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'before and after actions' do
|
42
|
+
before do
|
43
|
+
get :iiif_search,
|
44
|
+
params: { q: query_term, solr_document_id: parent_id }
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#set_search_builder' do
|
48
|
+
it 'should use IiifSearchBuilder' do
|
49
|
+
expect(controller.blacklight_config.search_builder_class).to eq(IiifSearchBuilder)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#set_access_headers' do
|
54
|
+
it 'should set the access control header' do
|
55
|
+
expect(response.headers['Access-Control-Allow-Origin']).to eq('*')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'render response' do
|
62
|
+
describe 'GET :iiif_search' do
|
63
|
+
before do
|
64
|
+
get :iiif_search,
|
65
|
+
params: { q: query_term, solr_document_id: parent_id }
|
66
|
+
end
|
67
|
+
let(:json) { JSON.parse(response.body) }
|
68
|
+
|
69
|
+
it 'returns a response' do
|
70
|
+
expect(response).to be_success
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns a IIIF AnnotationList' do
|
74
|
+
expect(json['@type']).to eq('sc:AnnotationList')
|
75
|
+
expect(json['resources']).not_to be_blank
|
76
|
+
expect(json['within']).not_to be_blank
|
77
|
+
expect(json['hits']).not_to be_blank
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'GET :iiif_search' do
|
82
|
+
before do
|
83
|
+
get :iiif_suggest,
|
84
|
+
params: { q: suggest_query_term, solr_document_id: parent_id }
|
85
|
+
end
|
86
|
+
let(:json) { JSON.parse(response.body) }
|
87
|
+
|
88
|
+
it 'returns a response' do
|
89
|
+
expect(response).to be_success
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns a IIIF TermList' do
|
93
|
+
expect(json['@type']).to eq('search:TermList')
|
94
|
+
expect(json['terms']).not_to be_blank
|
95
|
+
expect(json['terms'].first['match']).not_to be_nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
# Sample Solr docs for full-text search, representing book and pages.
|
2
|
+
# We duplicate full text in isbn_t field since this is the only text field
|
3
|
+
# that is indexed and stored in schema.xml AND searched by default
|
4
|
+
# requestHandler in solrconfig.xml in out-of-box Blacklight 6.* Solr config,
|
5
|
+
# which allows us to test highlighting and snippets.
|
6
|
+
---
|
7
|
+
- object_model_s:
|
8
|
+
- Book
|
9
|
+
id:
|
10
|
+
- 7s75dn48d
|
11
|
+
label_s:
|
12
|
+
- Raw denim raclette hexagon
|
13
|
+
title_t:
|
14
|
+
- Raw denim raclette hexagon
|
15
|
+
title_display: Raw denim raclette hexagon
|
16
|
+
author_t:
|
17
|
+
- Doe, Jane
|
18
|
+
author_display: Jane Doe
|
19
|
+
has_searchable_text_b:
|
20
|
+
- true
|
21
|
+
text_direction_s:
|
22
|
+
- ltr
|
23
|
+
timestamp:
|
24
|
+
- '2015-11-18T21:22:14.23Z'
|
25
|
+
- object_model_s:
|
26
|
+
- ImageFile
|
27
|
+
id:
|
28
|
+
- 7s75dn59x
|
29
|
+
is_file_of_s:
|
30
|
+
- 7s75dn48d
|
31
|
+
is_following_page_of_s:
|
32
|
+
- 7s75dn58n
|
33
|
+
is_page_of_s:
|
34
|
+
- 7s75dn48d
|
35
|
+
is_preceding_page_of_s:
|
36
|
+
- 7s75dn60x
|
37
|
+
page_type_s:
|
38
|
+
- Normal
|
39
|
+
hand_side_s:
|
40
|
+
- RIGHT
|
41
|
+
page_num_label_s:
|
42
|
+
- 5
|
43
|
+
page_num_label_type_s:
|
44
|
+
- original
|
45
|
+
text:
|
46
|
+
- In selecting sugar the confectioner must bear in mind that it is the foundation of all confectionery, and can not expect to manufacture iirst-class goods except from first-class material; hence, select only sugar that is perfectly dry and of uniform quality, and in white, hard, sparkling crystals; only such sugar will give satisfaction.
|
47
|
+
isbn_t:
|
48
|
+
- In selecting sugar the confectioner must bear in mind that it is the foundation of all confectionery, and can not expect to manufacture iirst-class goods except from first-class material; hence, select only sugar that is perfectly dry and of uniform quality, and in white, hard, sparkling crystals; only such sugar will give satisfaction.
|
49
|
+
timestamp:
|
50
|
+
- '2015-11-18T21:09:44.574Z'
|
51
|
+
- object_model_s:
|
52
|
+
- Book
|
53
|
+
id:
|
54
|
+
- 3j334603p
|
55
|
+
label_s:
|
56
|
+
- Quinoa gastropub snackwave
|
57
|
+
title_t:
|
58
|
+
- Quinoa gastropub snackwave
|
59
|
+
title_display: Quinoa gastropub snackwave
|
60
|
+
author_t:
|
61
|
+
- Doe, John
|
62
|
+
author_display: John Doe
|
63
|
+
has_searchable_text_b:
|
64
|
+
- true
|
65
|
+
text_direction_s:
|
66
|
+
- ltr
|
67
|
+
timestamp:
|
68
|
+
- '2015-11-18T19:32:49.014Z'
|
69
|
+
- object_model_s:
|
70
|
+
- ImageFile
|
71
|
+
id:
|
72
|
+
- 3j334628j
|
73
|
+
is_file_of_s:
|
74
|
+
- 3j334603p
|
75
|
+
is_page_of_s:
|
76
|
+
- 3j334603p
|
77
|
+
page_type_s:
|
78
|
+
- Normal
|
79
|
+
hand_side_s:
|
80
|
+
- RIGHT
|
81
|
+
page_num_label_s:
|
82
|
+
- 1
|
83
|
+
page_num_label_type_s:
|
84
|
+
- original
|
85
|
+
text:
|
86
|
+
- In choosing Colors for candy, certain qualifications are necessary. First, they must not fade or change when exposed to the light. Second, they must not be easily affected by acids or alkalies; hence, it has been difficult to produce colors that are reasonably permanent and at the same time harmless. Yet the following will be found as near perfect as any yet produced.
|
87
|
+
isbn_t:
|
88
|
+
- In choosing Colors for candy, certain qualifications are necessary. First, they must not fade or change when exposed to the light. Second, they must not be easily affected by acids or alkalies; hence, it has been difficult to produce colors that are reasonably permanent and at the same time harmless. Yet the following will be found as near perfect as any yet produced.
|
89
|
+
timestamp:
|
90
|
+
- '2015-11-18T17:27:35.361Z'
|
91
|
+
- object_model_s:
|
92
|
+
- ImageFile
|
93
|
+
id:
|
94
|
+
- 7s75dn58n
|
95
|
+
is_file_of_s:
|
96
|
+
- 7s75dn48d
|
97
|
+
# is_following_page_of_s:
|
98
|
+
# - 7s75dn57c
|
99
|
+
is_page_of_s:
|
100
|
+
- 7s75dn48d
|
101
|
+
is_preceding_page_of_s:
|
102
|
+
- 7s75dn59x
|
103
|
+
page_type_s:
|
104
|
+
- Normal
|
105
|
+
hand_side_s:
|
106
|
+
- LEFT
|
107
|
+
page_num_label_s:
|
108
|
+
- 4
|
109
|
+
page_num_label_type_s:
|
110
|
+
- original
|
111
|
+
text:
|
112
|
+
- Twelve pounds of sugar, two quarts of water, stir until dissolved, then add one full teaspoon of cream of tartar, (or if you wish to use glucose, add three pounds of same, do this when you are weighing your sugar, by hollowing out a place in the center of the sugar and pouring in the glucose). Now put on the steamer and let it remain until the batch begins to cook, then remove; in this way all the grains of sugar adhereing to the sides of the basin are washed down.
|
113
|
+
isbn_t:
|
114
|
+
- Twelve pounds of sugar, two quarts of water, stir until dissolved, then add one full teaspoon of cream of tartar, (or if you wish to use glucose, add three pounds of same, do this when you are weighing your sugar, by hollowing out a place in the center of the sugar and pouring in the glucose). Now put on the steamer and let it remain until the batch begins to cook, then remove; in this way all the grains of sugar adhereing to the sides of the basin are washed down.
|
115
|
+
timestamp:
|
116
|
+
- '2015-11-18T21:09:30.2Z'
|
117
|
+
- object_model_s:
|
118
|
+
- ImageFile
|
119
|
+
id:
|
120
|
+
- 7s75dn60x
|
121
|
+
is_file_of_s:
|
122
|
+
- 7s75dn48d
|
123
|
+
is_following_page_of_s:
|
124
|
+
- 7s75dn59x
|
125
|
+
is_page_of_s:
|
126
|
+
- 7s75dn48d
|
127
|
+
is_preceding_page_of_s:
|
128
|
+
- 7s75dn61x
|
129
|
+
page_type_s:
|
130
|
+
- Normal
|
131
|
+
hand_side_s:
|
132
|
+
- LEFT
|
133
|
+
page_num_label_s:
|
134
|
+
- 6
|
135
|
+
page_num_label_type_s:
|
136
|
+
- original
|
137
|
+
text:
|
138
|
+
- when it begins to boil, add six pounds of Almond Nuts, after haying picked out all shells and dusted the nuts thoroughly; stir slowly, keeping the sides of the basin well washed, until nuts are as brown as wished, and slide off the spatula easily when raised up
|
139
|
+
isbn_t:
|
140
|
+
- when it begins to boil, add six pounds of Almond Nuts, after haying picked out all shells and dusted the nuts thoroughly; stir slowly, keeping the sides of the basin well washed, until nuts are as brown as wished, and slide off the spatula easily when raised up
|
141
|
+
timestamp:
|
142
|
+
- '2015-11-19T21:09:44.574Z'
|
143
|
+
- object_model_s:
|
144
|
+
- ImageFile
|
145
|
+
id:
|
146
|
+
- 7s75dn61x
|
147
|
+
is_file_of_s:
|
148
|
+
- 7s75dn48d
|
149
|
+
is_following_page_of_s:
|
150
|
+
- 7s75dn60x
|
151
|
+
is_page_of_s:
|
152
|
+
- 7s75dn48d
|
153
|
+
is_preceding_page_of_s:
|
154
|
+
- 7s75dn62x
|
155
|
+
page_type_s:
|
156
|
+
- Normal
|
157
|
+
hand_side_s:
|
158
|
+
- RIGHT
|
159
|
+
page_num_label_s:
|
160
|
+
- 7
|
161
|
+
page_num_label_type_s:
|
162
|
+
- original
|
163
|
+
text:
|
164
|
+
- These Caramel receipts are the best in the worlds and the author questions the ability of any one to produce a Caramel that will excell them in any particular. After once making by either one of the following formulas, no other will be used, as they are worth a dozen times the price of this book, to any one wishing to give his trade a fine Caramel.
|
165
|
+
isbn_t:
|
166
|
+
- These Caramel receipts are the best in the worlds and the author questions the ability of any one to produce a Caramel that will excell them in any particular. After once making by either one of the following formulas, no other will be used, as they are worth a dozen times the price of this book, to any one wishing to give his trade a fine Caramel.
|
167
|
+
timestamp:
|
168
|
+
- '2015-11-20T21:09:44.574Z'
|
169
|
+
- object_model_s:
|
170
|
+
- ImageFile
|
171
|
+
id:
|
172
|
+
- 7s75dn62x
|
173
|
+
is_file_of_s:
|
174
|
+
- 7s75dn48d
|
175
|
+
is_following_page_of_s:
|
176
|
+
- 7s75dn61x
|
177
|
+
is_page_of_s:
|
178
|
+
- 7s75dn48d
|
179
|
+
is_preceding_page_of_s:
|
180
|
+
- 7s75dn63x
|
181
|
+
page_type_s:
|
182
|
+
- Normal
|
183
|
+
hand_side_s:
|
184
|
+
- LEFT
|
185
|
+
page_num_label_s:
|
186
|
+
- 8
|
187
|
+
page_num_label_type_s:
|
188
|
+
- original
|
189
|
+
text:
|
190
|
+
- Same as Hickory-nut Cups, except make a center of almond paste, colored light green; when on the slab, add to the clear batch a few drops of red color, some Nectar flavor, and fold all together, pull on the hook to a bright pink, and finish as before.
|
191
|
+
isbn_t:
|
192
|
+
- Same as Hickory-nut Cups, except make a center of almond paste, colored light green; when on the slab, add to the clear batch a few drops of red color, some Nectar flavor, and fold all together, pull on the hook to a bright pink, and finish as before.
|
193
|
+
timestamp:
|
194
|
+
- '2015-11-21T21:09:44.574Z'
|
195
|
+
- object_model_s:
|
196
|
+
- ImageFile
|
197
|
+
id:
|
198
|
+
- 7s75dn63x
|
199
|
+
is_file_of_s:
|
200
|
+
- 7s75dn48d
|
201
|
+
is_following_page_of_s:
|
202
|
+
- 7s75dn62x
|
203
|
+
is_page_of_s:
|
204
|
+
- 7s75dn48d
|
205
|
+
is_preceding_page_of_s:
|
206
|
+
- 7s75dn64x
|
207
|
+
page_type_s:
|
208
|
+
- Normal
|
209
|
+
hand_side_s:
|
210
|
+
- RIGHT
|
211
|
+
page_num_label_s:
|
212
|
+
- 9
|
213
|
+
page_num_label_type_s:
|
214
|
+
- original
|
215
|
+
text:
|
216
|
+
- as it looks cloudy, pour it out at once in a deep pan; this must be done quickly or it will become hard in the basin; when cold, remove it it from the pan in the same manner as the pan creams; mark it into bars or large squares for the counter.
|
217
|
+
isbn_t:
|
218
|
+
- as it looks cloudy, pour it out at once in a deep pan; this must be done quickly or it will become hard in the basin; when cold, remove it it from the pan in the same manner as the pan creams; mark it into bars or large squares for the counter.
|
219
|
+
timestamp:
|
220
|
+
- '2015-11-22T21:09:44.574Z'
|
221
|
+
- object_model_s:
|
222
|
+
- ImageFile
|
223
|
+
id:
|
224
|
+
- 7s75dn64x
|
225
|
+
is_file_of_s:
|
226
|
+
- 7s75dn48d
|
227
|
+
is_following_page_of_s:
|
228
|
+
- 7s75dn63x
|
229
|
+
is_page_of_s:
|
230
|
+
- 7s75dn48d
|
231
|
+
is_preceding_page_of_s:
|
232
|
+
- 7s75dn65x
|
233
|
+
page_type_s:
|
234
|
+
- Normal
|
235
|
+
hand_side_s:
|
236
|
+
- LEFT
|
237
|
+
page_num_label_s:
|
238
|
+
- 10
|
239
|
+
page_num_label_type_s:
|
240
|
+
- original
|
241
|
+
text:
|
242
|
+
- cut into small squares; cook just to a thread; remove and, with the spatula, work the syrup on the sides of the basin till it becomes cloudy; pour all on a wire sieve, having a pan under it to catch the syrup that drains from the Cocoanuts
|
243
|
+
isbn_t:
|
244
|
+
- cut into small squares; cook just to a thread; remove and, with the spatula, work the syrup on the sides of the basin till it becomes cloudy; pour all on a wire sieve, having a pan under it to catch the syrup that drains from the Cocoanuts
|
245
|
+
timestamp:
|
246
|
+
- '2015-11-23T21:09:44.574Z'
|
247
|
+
- object_model_s:
|
248
|
+
- ImageFile
|
249
|
+
id:
|
250
|
+
- 7s75dn65x
|
251
|
+
is_file_of_s:
|
252
|
+
- 7s75dn48d
|
253
|
+
is_following_page_of_s:
|
254
|
+
- 7s75dn64x
|
255
|
+
is_page_of_s:
|
256
|
+
- 7s75dn48d
|
257
|
+
# is_preceding_page_of_s:
|
258
|
+
# - 7s75dn65x
|
259
|
+
page_type_s:
|
260
|
+
- Normal
|
261
|
+
hand_side_s:
|
262
|
+
- RIGHT
|
263
|
+
page_num_label_s:
|
264
|
+
- 11
|
265
|
+
page_num_label_type_s:
|
266
|
+
- original
|
267
|
+
text:
|
268
|
+
- Take of dipping Cream any number of pounds desired, say five; put it into a basin inside of another one containing water; place on the fire; stir the Cream until dissolved, but not thin; add a few drops of red color, and flavor Nectar or Strawberry
|
269
|
+
isbn_t:
|
270
|
+
- Take of dipping Cream any number of pounds desired, say five; put it into a basin inside of another one containing water; place on the fire; stir the Cream until dissolved, but not thin; add a few drops of red color, and flavor Nectar or Strawberry
|
271
|
+
timestamp:
|
272
|
+
- '2015-11-24T21:09:44.574Z'
|