-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtag_scripture.php
More file actions
417 lines (393 loc) · 16.9 KB
/
Copy pathtag_scripture.php
File metadata and controls
417 lines (393 loc) · 16.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
<?php
/**
* tag_scripture.php: Receives text content, tags Scripture references, and returns tagged text.
*
* @package Sacred Wordpress
*/
require_once('biblebooks.php');
/* For testing purposes, we can request this page and provide a
* local file to process.
* e.g. http://localhost/wp-content/plugins/sacred-wordpress/tag_scripture.php?localfile=sample-text.txt
*/
if (strpos($_SERVER['REQUEST_URI'], 'tag_scripture.php')) {
// If this was the page requested
$localfile = filter_input(INPUT_GET, 'localfile', FILTER_SANITIZE_STRING,
FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW);
if ($localfile) {
header('Content-type: text/plain');
$text = file_get_contents($_GET["localfile"]);
$scriptureMarkup = new ScriptureMarkup($text);
$scriptureMarkup->tag_text($text);
echo $scriptureMarkup->get_text();
}
}
/* Otherwise this file only works when called directly by WordPress. */
class ScriptureMarkup {
private $last_book;
private $last_chapter;
private $text;
private $debug = false;
public function __construct($text) {
$this->last_book = '';
$this->last_chapter = 0;
$this->text = $this->tag_text($text);
}
/**
* Return the marked-up text.
*/
public function get_text() {
return $this->text;
}
/**
* Tag the Scripture references in the text (either provided in
* constructor or passed as parameter here.
*
* @param $text Optional passing of text to tag.
*/
public function tag_text($text = null) {
if (! $text) {
$text = $this->text;
}
/* If it hasn't been already, prepare the table of books. */
if (! isset($BIBLEBOOKS['BOOKS'])) {
BibleBooks::prepare_book_maps();
}
$count = 0;
if ($this->debug) {
echo "<!-- function mark_text() -->\n";
}
/* This should match:
* "1 Corinthians 13:1"
* "1 Corinthians 13:1-3"
* "1 Corinthians 13:1,3"
* "1 Corinthians 13"
* If a chapter was previously referred to, will match:
* v. 1 OR vv. 1-3
* Should match in the same citation: 1 Corinthians 13:1,3
* Should match in DIFFERENT citations: 1 Corinthians 13:1, 15:2 */
$text = preg_replace_callback(
"/\b # Match a word boundary --
# shouldn't be a part of anything else
(?|
(?i:<span[^>]+
class=\"[^\x22]*scriptureRef[^\x22]*\" # The scriptureRef class and
# any others
[^>]*> # Anything until the close of the tag
(.*?) # Everything until the next close-span, non-greedy
<\/span> # Hope we don't have nested spans; that would be screwy
) # Match that if it's there
|
( # First capture group: The name of the book
# The numeric prefix, e.g. *1* Corinthians
(?:(?:[1-4]|(?:I|II|III|IV)|(?:1st|2nd|3rd|4th)
|(?:First|Second|Third|Fourth))\b\s)?
# The first word of the title, first letter capital
(?:[A-Z][a-z]+\b\.?)
# Additional words of the title (up to two more)
(?:\s[oO]f(?:\s[tT]he)?\s[A-Z][a-z]+\.?)?
)
\s? # A space between the book name and the chapter-verse
( # Second capture group: The chapter-verse
(?:
(?: # A chapter-verse reference or range
# A basic chapter-verse reference, e.g. 3:16
\d+[:.]\d+
# With a possible range of verses (e.g. 3:16-19)
(?:[-\x{2013}] # \x{2013} = EN DASH
# End verse of a range,
# possibly in another chapter
(?:\d+[:.])?\d+)?
|
# Or can be a whole chapter
# (or a verse in single-chapter book)
\d+
# Or a possible range of chapters
(?:[-\x{2013}]\d+)?
)
(?:,\s* # A comma or other separator between refs
|\sand\s # Allow for cases e.g. 'John 6:37 and 10:27–30x'
|\sor\s
)?
# But don't match the next one if it appears to be
# another book.
(?!\s(?:[1-4]\s)?[A-Z][a-z]+)
)+ # But if possible, match a whole string of refs
)
)
\b
(?![^<]+>) # This should make sure we weren't in the middle
# of an HTML tag and accidentally match things
# like Web and IP addresses
/xu",
array($this, 'mark_scripture'), $text, -1, $count);
if ($this->debug) {
echo "<!-- preg_replace_callback() matched $count times. -->\n";
}
return $text;
}
/**
* The callback method for preg_replace_callback() in tag_text().
* This cuts a string of apparent Scripture references into pieces,
* determines if they are actually Scripture references after all,
* and if they are, tags them appropriately and returns the tagged
* string to be replaced in the text.
*
* @param $matches The matches passed by preg_replace_callback().
*/
private function mark_scripture($matches) {
if ($this->debug) {
print "<!-- Matched [0] '" . $matches[0] . "'";
if (isset ($matches[1])) { print ", [1] '" . $matches[1] . "'"; }
if (isset ($matches[2])) { print ", [2] '" . $matches[2] . "'"; }
if (isset ($matches[3])) { print ", [3] '" . $matches[3] . "'"; }
print " -->\n";
}
if (isset ($matches[3])) {
# Then it's something we recognized as a Scripture reference
# without a book or chapter (e.g. "v." or "vv.")
$book = '';
$verses = array($matches[3]);
} else {
# We presume $matches[1] and $matches[2] matched: a book
# reference followed by a string of verse references
$book = $matches[1];
# Split comma-separated references into array
$versestr = preg_replace('/ and | or /', ', ', $matches[2]);
# 'and' or 'or' in the string is really a list separator
# e.g. John 6:37–40 and 10:27–30
$verses = preg_split('/(?:,\s*)(?=\d+[.:]|$)/', $versestr);
# Look-ahead to be sure we don't split at compound verse
# references, e.g. John 3:3,5.
# Only split at full chapter:verse references,
# e.g. John 3:3, 6:35
if ($this->debug) {
echo "<!--"; var_dump($verses); echo "-->\n";
}
}
if (! $book) {
$book = '';
}
$return_string = $matches[0]; # The whole matched string
$i = -1;
foreach ($verses as $verse) {
$matched_verse = $verse;
$i++; # So will be 0 on first iteration (index of $verses)
if ($this->debug) {
print "<!-- verse match [$i]: '$verse' -->\n";
}
$flags = array();
if ($this->debug) {
print "<!-- Trying '$book', '$verse' -->\n";
}
if (! $book) {
# If we didn't match a book name,
# then assume the last named reference
if (! $this->last_book) { continue; }
if ($this->debug) {
print "<!-- No \$1 match, trying '$last_book' -->\n";
}
$book = $this->last_book;
$flags[] = "book_inferred";
if ($this->is_chapterless($book, $verse)) { # e.g. v. 5
if ($this->debug) {
print "<!-- Chapterless reference. Is chapter '$last_chapter'? -->\n";
}
if (! $this->last_chapter) { continue; }
$verse = $this->last_chapter . ":$verse";
$flags[] = "chapter_inferred";
}
} # If no $last_book set, i.e. if this is first possible
# reference, but there is o book name, this will fail
$ref = $this->is_it_scripture($book, $verse, $flags);
if ($ref) {
$flagstr = implode(" ", $flags);
if ($matches[1]) { $this->last_book = $book; }
if ($i == 0) { # The first one, with the book reference
$replace = "<span class=\"scriptureRef\"" .
($flagstr ? " data-scriptureref-flags=\"$flagstr\"" : "") .
" data-scriptureref=\"$ref\">" .
($matches[1] ? $matches[1] . " " : "") .
$matched_verse . "</span>";
$return_string = str_replace(
# Also match the book reference here if it exists
($matches[1] ? $matches[1] . " " : "") . $matched_verse,
$replace, $return_string);
} else { # Just the verse reference
$replace = "<span class=\"scriptureRef\"" .
($flagstr ? " data-scriptureref-flags=\"$flagstr\"" : "") .
" data-scriptureref=\"$ref\">" . $matched_verse . "</span>";
$return_string = str_replace($matched_verse, $replace, $return_string);
}
if ($this->debug) {
print "<!-- \$replace = '$replace' -->\n";
}
} else { # It's probably not a Scripture reference after all
FAIL:
# Don't replace anything; leave it as it is
}
}
if ($this->debug) {
print "<!-- \$return_string = $return_string -->\n\n";
}
return $return_string;
}
/**
* Passed an apparent Scripture reference, determines if it is
* actually Scripture after all.
*
* @param $book The book string matched in the regexp.
* @param $verse The chapter-verse string.
* @param &$flags Flags about the string we are passing around.
*/
private function is_it_scripture($book, $verse, &$flags) {
global $BIBLEBOOKS;
if ($this->debug) {
print "<!-- is_it_scripture('$book', '$verse')? -->\n";
}
if (! $book) {
# If there's no book, not even an inferred one, then we
# obviously have nothing to do here.
return 0;
}
# The cases when people spell out "First Corinthians" or use
# Roman or ordinal numerals are few and far between, but it's
# worthwhile to catch them.
$book = preg_replace('/^(I|1st|First)\b/', '1', $book);
$book = preg_replace('/^(II|2nd|Second)\b/', '2', $book);
$book = preg_replace('/^(II|3rd|Third)\b/', '3', $book);
$book = preg_replace('/^(II|4th|Fourth)\b/', '4', $book);
$book = preg_replace('/\./', '', $book);
if ($this->debug) {
print "<!-- Found ref: $book $verse = ";
}
# Whether the matched book string is '1 Cor' or '1Co' or
# '1 Corinthians', it ought to match in the [BOOKS] table.
if (array_key_exists($book, $BIBLEBOOKS['BOOKS'])) {
$book = $BIBLEBOOKS['BOOKS'][$book];
if (array_key_exists($book, $BIBLEBOOKS['DEUTERO'])) {
$flags[] = 'deutero';
}
if ($this->is_single_chapter($book)) {
# If the book is a single chapter (e.g. 1 John), at
# least the Bibles.org API likes to explicitly
# identify it with a chapter reference.
$verse = "1:$verse";
$flags[] = 'short';
$flags[] = 'chapter_inferred';
}
$verse = preg_replace('/\x{2013}/u', '-', $verse);
# EN DASH to hyphen: Makes it easier to deal with below.
$verse = preg_replace('/\./', ':', $verse);
if (! preg_match('/[-,]/', $verse)) {
if (! preg_match('/:/', $verse) and
! in_array('book_inferred', $flags)) {
# e.g. 1 Corinthians 13, but not just the numeral 13
# detached from any other context.
$flags[] = 'whole_chapter';
} else { # e.g. 1 Corinthians 13:1
$flags[] = 'single_verse';
}
}
if (preg_match('/-/', $verse)) {
$flags[] = 'range';
}
if (preg_match('/,/', $verse)) {
# A case like John 3:3,5, which we will treat
# as a single reference (even if the API doesn't).
$flags[] = 'compound';
}
# Save the chapter we matched this time, in case the next
# reference is a verse in the same inferred chapter.
$this->last_chapter = $this->last_chapter($book, $verse);
$ref = "$book $verse";
if ($this->debug) {
print "'$ref' -->\n";
}
return $ref;
} else {
# It wasn't Scripture after all.
if ($this->debug) {
print "NOT SCRIPTURE -->\n";
}
return 0;
}
}
/**
* Is this a book composed of a single chapter? (i.e. Jude, 2 John)
*
* @param $book The name of a Bible book.
*/
private function is_single_chapter($book) {
global $BIBLEBOOKS;
$book = $BIBLEBOOKS['BOOKS'][$book]; # The canonical book name
if ($BIBLEBOOKS['CHAPTERS'][$book] == 1) {
return 1;
}
}
/**
* Given a book and verse reference, is this a reference to verses
* without a chapter reference? (e.g. 9-10, not 5:9-10)
* (We already know that there was no actual book reference,
* only an inferred book -- since $matches[1] did not match above).
*
* @param $book The inferred Bible book.
* @param $verse The chapter-verse string
*/
private function is_chapterless($book, $verse) {
if ($this->is_single_chapter($book)) {
return 0; # Because Chapter 1 will be inferred later
}
if (preg_match('/[:.]/', $verse, $matches)) {
return 0; # Then an explicit chapter is given here, e.g. 13:10-15
}
# Otherwise -- something like 15 or 10-15.
return 1;
}
/**
* Identify the chapter element of the reference
* for use in forward inferences.
* (This doesn't actually return the last chapter, but sets the
* $last_chapter for use in the future.)
*
* @param $book The book string.
* @param $verse The chapter-verse string.
*/
function last_chapter($book, $verse) {
#
global $BIBLEBOOKS;
# Get the last chapter referred to
$book = $BIBLEBOOKS['BOOKS'][$book];
if ($BIBLEBOOKS['CHAPTERS'][$book] == 1) {
# Only one chapter in this book, so no chapter reference.
# We can quit already.
return 1;
}
# This regexp takes a string like 9:10-9:15, 9:10-15, etc.
# and picks out the chapter part.
if (preg_match('/(\d+)([:.]\d+)?(?:[-\x{2013}](\d+)([:.]\d+))?/u',
$verse, $matches)) {
$begin_chapter = '';
$begin_verse = '';
$end_chapverse = '';
$end_verse = '';
if (array_key_exists(1, $matches)) {
$begin_chapter = $matches[1];
}
if (array_key_exists(2, $matches)) {
$begin_verse = $matches[2];
}
if (array_key_exists(3, $matches)) {
$end_chapverse = $matches[3];
}
if (array_key_exists(4, $matches)) {
$end_verse = $matches[4];
}
if (! $end_chapverse or ! $end_verse) {
# Either 3:3 OR 3:3-5
return $begin_chapter;
} else { # 3:3-4:1
return $end_chapverse;
}
}
}
}