-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathfrus.sch
More file actions
725 lines (679 loc) · 41.7 KB
/
Copy pathfrus.sch
File metadata and controls
725 lines (679 loc) · 41.7 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
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt3"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ckbk="http://www.oreilly.com/XSLTCookbook"
xmlns:sqf="http://www.schematron-quickfix.com/validator/process"
xmlns:functx="http://www.functx.com">
<title>FRUS TEI Rules</title>
<p>FRUS TEI Rules Schematron file ($Id: frus.sch 4528 2016-02-22 21:33:23Z joewiz $)</p>
<p>This schematron adds FRUS TEI-specific rules to the more generic tei-all.rng RelaxNG Schema
file. FRUS TEI files that validate against *both* schema files are considered valid FRUS TEI
files.</p>
<ns prefix="tei" uri="http://www.tei-c.org/ns/1.0"/>
<ns prefix="frus" uri="http://history.state.gov/frus/ns/1.0"/>
<ns prefix="ckbk" uri="http://www.oreilly.com/XSLTCookbook"/>
<ns prefix="xml" uri="http://www.w3.org/XML/1998/namespace"/>
<extends href="dates-only-initial-review.sch"/>
<!-- Define variables used by other patterns -->
<let name="xml-ids" value="//*/@xml:id"/>
<let name="vol-ids"
value="
if (doc-available('https://history.state.gov/services/volume-ids')) then
doc('https://history.state.gov/services/volume-ids')//volume-id
else
doc('volume-ids-snapshot.xml')//volume-id"/>
<!-- Defined in dates-only-initial-review <let name="category-ids" value="//tei:category/@xml:id"/> -->
<let name="persName-ids" value="//tei:persName/@xml:id"/>
<let name="term-ids" value="//tei:term/@xml:id"/>
<let name="rendition-ids" value="//tei:rendition/@xml:id"/>
<let name="documents" value="//tei:div[@type = 'document']"/>
<let name="vol-id" value="/tei:TEI/@xml:id"/>
<let name="available-images"
value="doc(concat('https://history.state.gov/services/volume-images?volume=', $vol-id))//image"/>
<pattern id="filename-id-check">
<rule context="/tei:TEI">
<assert test="@xml:id">Volume's root element is missing an @xml:id; it should correspond
to the volume ID.</assert>
</rule>
<rule context="/tei:TEI/@xml:id">
<let name="basename" value="replace(base-uri(.), '^.*/(.*?)$', '$1')"/>
<assert test="$basename = concat(., '.xml')">volume id <value-of select="."/> does not
match filename <value-of select="$basename"/></assert>
<assert test=". = $vol-ids">Invalid TEI/@xml:id: <value-of select="."/>. No known volume
ID corresponds to this volume's @xml:id.</assert>
</rule>
</pattern>
<pattern id="processing-instruction-check">
<rule context="/processing-instruction()">
<assert test="not(.)">Please remove root level processing instructions before
delivery.</assert>
</rule>
</pattern>
<pattern id="tei-header-checks">
<title>TEI Header Checks</title>
<rule context="tei:fileDesc">
<assert test="count(tei:titleStmt) eq 1">fileDesc needs exactly one titleStmt</assert>
<assert test="count(tei:publicationStmt) eq 1">fileDesc needs exactly one
publicationStmt</assert>
<assert test="count(tei:sourceDesc) eq 1">fileDesc needs exactly one sourceDesc</assert>
<assert test="count(child::element()) eq 3">fileDesc can only have three child elements:
titleStmt, publicationStmt, and sourceDesc</assert>
</rule>
<rule context="tei:title[parent::tei:titleStmt]">
<assert test="./@type = ('complete', 'series', 'sub-series', 'volume-number', 'volume')"
>title/@type='<value-of select="@type"/>' is an invalid value. Only the
following values are allowed: complete, series, sub-series, volume-number,
volume</assert>
<assert test="not(./element())">titleStmt/title cannot contain child elements, only a
single text node</assert>
</rule>
<rule context="tei:titleStmt">
<assert test="count(tei:title[@type = 'complete']) = 1">titleStmt needs exactly one
title of @type 'complete'</assert>
<assert test="count(tei:title[@type = 'series']) = 1">titleStmt needs exactly one title
of @type 'series'</assert>
<assert test="count(tei:title[@type = 'sub-series']) = 1">titleStmt needs exactly one
title of @type 'sub-series'</assert>
<assert test="count(tei:title[@type = 'volume-number']) = 1">titleStmt needs exactly one
title of @type 'volume-number'</assert>
<assert test="count(tei:title[@type = 'volume']) = 1">titleStmt needs exactly one title
of @type 'volume'</assert>
<assert test="count(distinct-values(tei:title/@type)) = count(tei:title)">There can only
be one of each @type of title</assert>
</rule>
<rule context="tei:publicationStmt">
<assert test="count(tei:publisher) = 1">publicationStmt needs exactly one
publisher</assert>
<assert test="count(tei:pubPlace) = 1">publicationStmt needs exactly one
pubPlace</assert>
<assert test="count(tei:date) = 2">publicationStmt needs exactly two dates: a volume
publication date and volume content date</assert>
<assert test="tei:idno/@type = ('dospubno', 'frus', 'isbn-13', 'isbn-10')"
>idno/@type='<value-of select="@type"/>' is an invalid value. Only the following
values are allowed: dospubno, frus, isbn-13, isbn-10</assert>
<assert
test="count(tei:idno[@type = 'frus']) = 1 and tei:idno[@type = 'frus'] = $vol-ids"
>publicationStmt needs exactly one idno of type 'frus', and it must be a defined
volume ID</assert>
</rule>
</pattern>
<pattern id="list-checks">
<title>List Checks</title>
<rule context="tei:list">
<assert
test="./@type = ('participants', 'subject', 'index', 'terms', 'names', 'toc', 'references', 'from', 'to', 'simple', 'sources', 'from') or not(./@type)"
>list/@type='<value-of select="@type"/>' is an invalid value. Only the following
values are allowed: participants, subject, index, terms, names, toc, references,
from, to, simple, sources</assert>
</rule>
<rule context="tei:item[ancestor::tei:div/@xml:id = 'terms' and not(child::tei:list)]">
<assert test=".//tei:term/@xml:id" sqf:fix="add-term add-xml-id">Missing term element
with @xml:id attribute. Entries in the list of terms & abbreviations must have
an @xml:id attribute</assert>
<let name="first-hi" value="(.//tei:hi)[1]"/>
<let name="term"
value="
if (ends-with($first-hi, ',')) then
replace($first-hi, ',$', '')
else
$first-hi"/>
<let name="id" value="concat('t_', replace($term, '\W', ''), '_1')"/>
<!-- the fix takes two passes. hopefully we'll find a way to do this in a single pass
see https://www.oxygenxml.com/forum/topic14270.html. -->
<sqf:fix id="add-term" use-when="not(.//tei:term)">
<sqf:description>
<sqf:title>Add a missing term element</sqf:title>
</sqf:description>
<sqf:replace match="(.//tei:hi)[1]">
<xsl:element name="tei:hi">
<xsl:attribute name="rend" select="./@rend"/>
<xsl:element name="tei:term">
<xsl:value-of select="$term"/>
</xsl:element>
<xsl:text>,</xsl:text>
</xsl:element>
</sqf:replace>
</sqf:fix>
<sqf:fix id="add-xml-id" use-when="exists(.//tei:term)">
<sqf:description>
<sqf:title>Add a missing @xml:id</sqf:title>
</sqf:description>
<sqf:add match=".//tei:term" target="xml:id" node-type="attribute" select="$id"/>
</sqf:fix>
<assert test="not(tei:term/tei:hi/@rend = 'strong') and not(ends-with(tei:term, ','))"
>Improper nesting of hi and term elements (the hi/@rend=strong tag must surround the
term element), and/or improper placement of trailing punctuation mark (the trailing
comma must lie outside the term element)</assert>
</rule>
<rule context="tei:item[parent::tei:list/@type = 'persons']">
<assert test=".//tei:persName/@xml:id">Missing term element with @xml:id attribute.
Entries in the list of terms & abbreviations must have an @xml:id
attribute</assert>
<assert test="tei:persName/tei:hi/@rend = 'strong' or ends-with(tei:persName, ',')"
>Improper nesting of hi and persName (the hi/@rend=strong tag must surround the
persName element), and/or improper placement of trailing punctuation mark (the
trailing comma must lie outside the persName element)</assert>
</rule>
</pattern>
<pattern id="att-rend-checks">
<title>Rend Attribute Value Checks</title>
<rule context="tei:hi">
<assert
test="./@rend = ('strong', 'italic', 'smallcaps', 'roman', 'underline', 'sub', 'superscript')"
>hi/@rend='<value-of select="@rend"/>' is an invalid value. Only the following
values are allowed: strong, italic, smallcaps, roman, underline, sub,
superscript</assert>
</rule>
<rule context="tei:p">
<!-- note that the 4th entry here lets quite a few known "invalid" values pass this test; we will perform this pass of checks at a later stage. TODO. -->
<assert
test="./@rend = ('strong', 'italic') or ./@rend = ('sectiontitleital') or ./@rend = ('center', 'right', 'flushleft') or ./@rend = ('sourceparagraphspaceafter', 'sourceparagraphfullindent', 'sourceparagraphtightspacing', 'sourceheadcenterboldbig', 'sourcearchiveboldbig') or not(./@rend)"
>p/@rend='<value-of select="@rend"/>' is an invalid value. Only the following
values are allowed: strong, italic, sectiontitleital, center, right,
flushleft</assert>
</rule>
<rule context="tei:del">
<assert test="./@rend = 'strikethrough'">del/@rend='<value-of select="@rend"/>' is an
invalid value. Only the following value is allowed: strikethrough</assert>
</rule>
<rule context="tei:note[@rend]">
<assert test="./@rend = 'inline'">note/@rend='<value-of select="@rend"/>' is an
invalid value. Only the following value is allowed: inline</assert>
</rule>
</pattern>
<pattern id="note-type-checks">
<title>Note type checks</title>
<rule context="tei:note[@type]">
<assert test="./@type = ('source', 'summary')">note/@type='<value-of select="@type"/>'
is an invalid value. Only the following values are allowed: source, summary</assert>
</rule>
</pattern>
<pattern id="att-rendition-checks">
<title>Rendition Attribute Value Checks</title>
<rule context="@rendition">
<assert
test="
every $rendition-ref in (tokenize(., '\s+') ! substring-after(., '#'))
satisfies $rendition-ref = $rendition-ids"
>The rendition ID '<value-of select="."/>' is not defined in the teiHeader's list of
renditions: <value-of select="string-join($rendition-ids, ', ')"/></assert>
</rule>
</pattern>
<pattern id="div-checks">
<title>Div Type Attribute Value Checks</title>
<rule context="tei:div">
<assert
test="./@type = ('document', 'chapter', 'subchapter', 'document-group', 'compilation', 'section', 'toc')"
>div/@type='<value-of select="@type"/>' is an invalid value. Only the following
values are allowed: document, chapter, subchapter, compilation, section,
toc</assert>
</rule>
<rule context="tei:div">
<assert
test="./@subtype = ('editorial-note', 'errata_document-numbering-error', 'historical-document', 'index')"
>div/@subtype='<value-of select="@subtype"/>' is an invalid value. Only the
following values are allowed: editorial-note, errata_document-numbering-error,
historical-document, index</assert>
</rule>
</pattern>
<pattern id="div-child-checks">
<title>Compilation/Chapter/Subchapter Nesting Checks</title>
<rule
context="tei:div[@type = ('compilation', 'chapter', 'subchapter')][not(@subtype = ('index', 'referral', 'editorial-note'))]">
<assert role="warn"
test=".//tei:div[@type = 'document'] or .//tei:div[@subtype = ('index', 'referral', 'editorial-note')]"
>This <value-of select="@type"/> does not contain a div/@type='document'. Please
inspect to verify encoding accuracy.</assert>
</rule>
<rule
context="tei:div[@type = 'document']">
<assert role="warn"
test="not(preceding-sibling::tei:div[1][@type = ('compilation', 'chapter', 'subchapter')] or following-sibling::tei:div[1][@type = ('compilation', 'chapter', 'subchapter')])"
>This document is preceded or followed by a <value-of select="@type"/>. Please
inspect to verify accuracy hierarchy.</assert>
</rule>
</pattern>
<pattern id="div-numbering-checks">
<title>Document Div Numbering Checks</title>
<rule context="tei:div[@type = 'document'][@n castable as xs:integer]">
<assert
test="not(./preceding::tei:div[@type = 'document']) or ./@n = (./preceding::tei:div[@n castable as xs:integer][1]/@n + 1)"
>Document numbering mismatch. Document div/@n numbering must be
consecutive.</assert>
</rule>
<rule context="tei:div[@type = 'document']">
<assert test="not(matches(./@n, '^\[.+?\]$'))" role="warning">Document's @n is encased
in square brackets: "[]". Only use in the rare circumstance that the volume has a
block of unnumbered documents outside the normal stream of numbered
documents.</assert>
<assert test="matches(./@n, '^\[.+?\]$') or ./@n castable as xs:integer" role="warning">Non-number
component found in document number "<value-of select="@n"/>"</assert>
</rule>
<rule context="tei:body">
<assert
test="count($documents[@n castable as xs:integer]) = 0 or count($documents[@n castable as xs:integer]) = $documents[@n castable as xs:integer][last()]/@n - $documents[@n castable as xs:integer][1]/@n + 1"
>Document numbering mismatch. The total number of documents with integer-@n values
(<value-of select="count($documents[@n castable as xs:integer])"/>) should equal
the difference between the first and final documents' numbers (<value-of
select="$documents[@n castable as xs:integer][last()]/@n"/> - <value-of
select="$documents[@n castable as xs:integer][1]/@n"/> + 1 = <value-of
select="$documents[@n castable as xs:integer][last()]/@n - $documents[@n castable as xs:integer][1]/@n + 1"
/>), or the number of documents must be 0 (indicating a volume not yet
digitized).</assert>
</rule>
</pattern>
<pattern id="frus-attachment-checks">
<title>frus:attachment Checks</title>
<rule context="frus:attachment">
<assert test="not(tei:div)">A frus:attachment element must not contain a child
tei:div</assert>
<assert test="not(.//tei:head = '(Editorial Note|Editor’s Note)')">Treat editorial notes
as full documents, not as attachments</assert>
<!-- experimental frus:attachment @xml:id -->
<!--
<assert test="@xml:id">Missing @xml:id for frus:attachment</assert>
<assert test="matches(@xml:id, concat('^', ./ancestor::tei:div[@xml:id][1]/@xml:id, 'at', index-of(./ancestor::tei:div[@xml:id][1]/frus:attachment, .)))">Incorrectly formed @xml:id '<value-of select="@xml:id"/>'</assert>
-->
</rule>
</pattern>
<pattern id="table-rows-cols-checks">
<rule context="tei:table[@rows and @cols]">
<assert test="./@rows = count(child::tei:row)">The number of rows asserted in the table
element, <value-of select="./@rows"/>, does not equal the total number of row tags,
<value-of select="count(child::tei:row)"/>
</assert>
<!-- this test relies on the XPath 3.0 simple map operator (!) and is waiting on oXygen support. until then, the test will fail in the case of row-spanning cells. -->
<!--<assert test="sum(./@rows * ./@cols)=count(descendant::tei:cell)">The number of cells asserted in the table element (<value-of select="./@rows"/> rows * <value-of select="./@cols"/> columns means there should be <value-of select="sum(./@rows * ./@cols)"/> cells), does not equal the total number of cells in the table, <value-of select="count(descendant::tei:cell[not(@rows)]) + descendant::tei:cell ! ./@rows"/></assert>-->
</rule>
</pattern>
<pattern id="footnote-id-checks">
<title>Footnote ID Checks</title>
<rule context="tei:note[@xml:id and ancestor::tei:div/@type = 'document']">
<assert test="substring-before(./@xml:id, 'fn') = ./ancestor::tei:div[1]/@xml:id"
>Footnote ID mismatch. Document ID portion of footnote @xml:id '<value-of
select="./@xml:id"/>' must match its document's @xml:id '<value-of
select="./ancestor::tei:div[1]/@xml:id"/>'.</assert>
</rule>
</pattern>
<pattern id="element-nesting-checks">
<title>Element Nesting Checks</title>
<rule context="tei:note[not(@rend = 'inline')]">
<assert test="count(descendant::tei:note) = 0">A footnote cannot be nested inside
another footnote, see note/@xml:id='<value-of select="./@xml:id"/>'.</assert>
</rule>
<rule context="tei:list">
<assert test="count(./tei:p) = 0">A list element cannot contain a direct child paragraph
element.</assert>
<assert test="count(.//tei:table) = 0">A list element cannot contain a table
element.</assert>
</rule>
</pattern>
<pattern id="pointer-checks">
<title>Ref and Pointer Checks</title>
<rule
context="tei:ref[starts-with(@target, '#pg') and substring-after(@target, 'pg_') castable as xs:integer and ./preceding-sibling::node()[1] = '–' and ./preceding-sibling::node()[2]/self::tei:ref]">
<assert
test="xs:integer(substring-after(@target, '#pg_')) gt xs:integer(substring-after(preceding-sibling::node()[2]/@target, '#pg_'))"
>Invalid page range: <value-of select="preceding-sibling::node()[2]/@target"
/>–<value-of select="@target"/> (see #<value-of
select="./ancestor::tei:div[1]/@xml:id"/>
<value-of
select="
if (./ancestor::tei:div[1]/@xml:id = 'index') then
concat(' under ', string-join(subsequence(tokenize(./ancestor::tei:item[1], '\s+'), 1, 2), ' '), ',')
else
()"
/> and <value-of select="./preceding::tei:pb[1]/@facs"/>.tif).</assert>
</rule>
<rule
context="tei:ref[starts-with(@target, '#pg') and not(substring-after(@target, 'pg_') castable as xs:integer) and ./preceding-sibling::node()[1] = '–' and ./preceding-sibling::node()[2]/self::tei:ref]">
<assert
test="xs:integer(ckbk:roman-to-number(substring-after(@target, '#pg_'))) gt xs:integer(ckbk:roman-to-number(substring-after(preceding-sibling::node()[2]/@target, '#pg_')))"
>Invalid page range: <value-of select="preceding-sibling::node()[2]/@target"
/>–<value-of select="@target"/> (<value-of
select="ckbk:roman-to-number(substring-after(preceding-sibling::node()[2]/@target, '#pg_'))"
/>–<value-of select="ckbk:roman-to-number(substring-after(@target, '#pg_'))"/>;
see #<value-of select="./ancestor::tei:div[1]/@xml:id"/>
<value-of
select="
if (./ancestor::tei:div[1]/@xml:id = 'index') then
concat(' under ', string-join(subsequence(tokenize(./ancestor::tei:item[1], '\s+'), 1, 2), ' '), ',')
else
()"
/> and <value-of select="./preceding::tei:pb[1]/@facs"/>.tif).</assert>
</rule>
<rule context="tei:ref[starts-with(@target, 'frus')]">
<assert
test="
if (contains(@target, '#') and substring-before(@target, '#') = $vol-ids) then
(substring-before(@target, '#') = $vol-ids and (if (doc-available(concat('../volumes/', substring-before(@target, '#'), '.xml'))) then
doc(concat('../volumes/', substring-before(@target, '#'), '.xml'))//*/@xml:id = substring-after(@target, '#')
else
if (doc-available(concat('../../frus-not-yet-reviewed/volumes/', substring-before(@target, '#'), '.xml'))) then
doc(concat('../../frus-not-yet-reviewed/volumes/', substring-before(@target, '#'), '.xml'))//*/@xml:id = substring-after(@target, '#')
else (: allow this check to pass if you don't have our exact directory structure :)
true()))
else
@target = $vol-ids"
>ref/@target='<value-of select="@target"/>' is an invalid value. No volume's ID
and/or target element corresponds to this ref/@target value (or, possibly, the
volume has not yet been published).</assert>
</rule>
<rule context="tei:ref[starts-with(@target, '#')]">
<assert test="substring-after(@target, '#') = $xml-ids">ref/@target='<value-of
select="@target"/>' is an invalid value. No element's @xml:id corresponds to
this value.</assert>
</rule>
<rule context="tei:ref">
<assert
test="starts-with(@target, '#') or starts-with(@target, 'http') or starts-with(@target, 'mailto')"
>Invalid ref/@target='<value-of select="@target"/>'. If this is an internal
cross-reference, it needs a "#" prefix.</assert>
</rule>
<rule context="tei:persName[starts-with(@corresp, '#')]">
<assert test="substring-after(@corresp, '#') = $persName-ids"
>persName/@corresp='<value-of select="@corresp"/>' is an invalid value. No
persName has been defined with an @xml:id corresponding to this value.</assert>
</rule>
<rule context="tei:persName[starts-with(@corresp, 'frus')]">
<assert
test="
substring-before(@corresp, '#') = $vol-ids and (if (doc-available(concat('../volumes/', substring-before(@corresp, '#'), '.xml'))) then
doc(concat('../volumes/', substring-before(@corresp, '#'), '.xml'))//*/@xml:id = substring-after(@corresp, '#')
else
if (doc-available(concat('../../frus-not-yet-reviewed/volumes/', substring-before(@corresp, '#'), '.xml'))) then
doc(concat('../../frus-not-yet-reviewed/volumes/', substring-before(@corresp, '#'), '.xml'))//*/@xml:id = substring-after(@corresp, '#')
else
false())"
>persName/@corresp='<value-of select="@corresp"/>' is an invalid value. No
persName has been defined in that volume with an @xml:id corresponding to this
value.</assert>
</rule>
<rule context="tei:gloss[@target]">
<assert test="substring-after(@target, '#') = $term-ids">gloss/@target='<value-of
select="@target"/>' is an invalid value. No term has been defined with an
@xml:id corresponding to this value.</assert>
</rule>
<rule context="@xml:id">
<assert test="count(index-of($xml-ids, .)) eq 1">@xml:id=<value-of select="."/>. Two
elements cannot have the same xml:id attribute.</assert>
</rule>
</pattern>
<pattern id="ref-to-page-footnote-check">
<title>Ref to Page Footnote Check</title>
<rule context="tei:ref[contains(@target, '#pg_')]">
<assert test="not(following-sibling::node()[1]/self::tei:hi = 'n')">Please italicize 'n'
inside the ref.</assert>
</rule>
</pattern>
<pattern id="empty-missing-content-checks">
<title>Empty/Missing Content Checks</title>
<rule context="tei:p | tei:gloss | tei:persName | tei:placeName | tei:seg">
<assert test="count(./node()) gt 0 and normalize-space(.) ne ''"><value-of
select="name(.)"/> elements cannot be empty.</assert>
</rule>
<rule context="tei:editor">
<assert test="./parent::tei:titleStmt">An editor element is allowed only in the
tei:titleStmt element.</assert>
<assert role="warn" test="count(./node()) gt 0">In contemporary publications, an editor
element should not be empty. (In the back catalog, the editor element may be empty
by necessity.)</assert>
<assert test="./@role">An editor element needs a @role attribute.</assert>
<assert test="string-length(./@role) gt 0">An editor/@role attribute cannot be
empty.</assert>
<assert test="@role = ('primary', 'general')">The value of editor/@role must be: primary
or general</assert>
</rule>
<rule context="tei:div">
<assert test="count(tei:head) = 1">A div must have a head child.</assert>
<assert test="./@xml:id">A div must have an @xml:id attribute.</assert>
</rule>
<rule context="tei:head">
<assert test="count(preceding-sibling::tei:head) = 0">There can only be one head
element.</assert>
<assert test="string-length(normalize-space(.)) gt 0">Head elements cannot be
empty.</assert>
</rule>
<rule context="tei:lb|tei:pb">
<assert test="empty(./node())" sqf:fix="delete-child-content"><value-of select="./name()"/> elements must be empty (no child content).</assert>
<sqf:fix id="delete-child-content">
<sqf:description>
<sqf:title>Delete child content</sqf:title>
</sqf:description>
<sqf:delete match="./node()"/>
</sqf:fix>
</rule>
</pattern>
<pattern id="source-note-checks">
<title>Source Note checks</title>
<!-- limit this rule to volumes covering post-1950 or published after 1995, when source note conventions begin to conform to these rules -->
<rule
context="tei:div[@subtype eq 'historical-document'][root(.)/tei:TEI/tei:teiHeader[.//tei:date[@type eq 'content-date']/@notBefore ge '1950' or .//tei:date[@type eq 'publication-date'] gt '1995']]">
<let name="source-note"
value="(tei:note[@type eq 'source' and @rend eq 'inline'], tei:head/tei:note[@type eq 'source'], tei:head/tei:note//tei:seg[@type eq 'source'])[1]"/>
<let name="source-note-content"
value="
(if ($source-note/tei:p) then
$source-note/tei:p[1]
else
$source-note)[1] => normalize-space()"/>
<assert test="exists($source-note)">Source note is missing</assert>
<!-- if the source note is the 1st element in the div, it's a pre-1955 document, before the "Source:" convention was present/consistent -->
<assert test="
if (./element()[1] is $source-note) then
true()
else
(starts-with($source-note-content, 'Source:') or matches($source-note-content, '^\[Source:(.+)\]$'))"
>Source note doesn't begin with 'Source:' or '[Source:...]': <value-of
select="$source-note-content"/></assert>
</rule>
</pattern>
<pattern id="milestone-checks">
<title>Milestone checks</title>
<rule
context="(tei:pb | tei:lb)[not(parent::tei:front | parent::tei:body | parent::tei:back)]">
<assert
test="count(preceding-sibling::element()) ge 1 or normalize-space(string-join(preceding-sibling::node())) ne ''"
>A pb or lb element cannot be the first element in a parent element (except front, body, and back).</assert>
<assert
test="count(following-sibling::element()) ge 1 or normalize-space(string-join(following-sibling::node())) ne ''"
>A pb or lb element cannot be the last element in a parent element (except front, body, and back).</assert>
</rule>
</pattern>
<pattern id="unwanted-footnote-whitespace-checks">
<title>Prevent unwanted footnote whitespace checks</title>
<rule context="tei:note[not(@rend eq 'inline' or preceding-sibling::node()[2] instance of element(tei:note))]">
<let name="first-preceding-sibling-node" value="preceding-sibling::node()[1]"/>
<assert
test="if ($first-preceding-sibling-node instance of text()) then normalize-space($first-preceding-sibling-node) ne '' else true()"
>Whitespace is not allowed before a footnote (except between consecutive footnotes)</assert>
</rule>
</pattern>
<pattern id="required-footnote-whitespace-checks">
<title>Ensure required footnote whitespace checks</title>
<rule context="tei:note">
<assert test="not(preceding-sibling::node()[1] instance of element(tei:note))"
>Whitespace is required between consecutive footnotes</assert>
</rule>
</pattern>
<pattern id="pb-lb-whitespace-checks">
<title>Ensure balanced whitespace around breaks</title>
<rule context="tei:lb|tei:pb">
<assert test="
(matches(preceding::text()[1], '\s$') and matches(following::text()[1], '^\s'))
or
(matches(preceding::text()[1], '[^\s]$') and matches(following::text()[1], '^[^\s]'))
"
>A line or page break element should have whitespace both before and after, or neither when the break splits a word</assert>
</rule>
</pattern>
<pattern id="punctuation-orientation-and-placement-checks">
<title>Punctuation orientation & placement checks</title>
<!-- check all text nodes (except those in catDesc elements,
since the imported frus-dates taxonomy contains straight quotes) -->
<rule context="text()[not(parent::tei:catDesc|parent::tei:rendition)]">
<!-- flag unexpected space after an open quote -->
<assert role="warn" test="not(matches(., '[“‘] '))">Curly open quotation mark appears in
[<value-of
select="string-join(analyze-string(normalize-space(.), '\S*[“‘] \S*')/fn:match, '; ')"
/>]. Fix orientation? Delete trailing space?</assert>
<!-- flag unexpected space before a close quote. exclude abbreviated numbers, e.g., ’70s -->
<assert role="warn" test="not(matches(., ' [”’][^\d]'))">Curly close quotation mark
appears in [<value-of
select="string-join(analyze-string(normalize-space(.), '\S* [”’][^\d]\S*')/fn:match, '; ')"
/>]. Fix orientation? Delete leading space?</assert>
<!-- flag mixed use of straight and curly. exclude measures of feet and inches, e.g., 6' or 5'2" -->
<assert role="warn" test="not(matches(., '\D[''"]') and matches(., '[‘’“”]'))"
>Mixed use of straight and curly quotes. Change straight to curly? Straight:
[<value-of
select="string-join(analyze-string(normalize-space(.), '\S*[''"]\S*')/fn:match, '; ')"
/>] // Curly: [<value-of
select="string-join(analyze-string(normalize-space(.), '\S*[‘’“”]\S*')/fn:match, '; ')"
/>] </assert>
<!-- flag all other instances of straight quotes. exclude measures of feet and inches -->
<assert role="warn" test="not(matches(., '\D[''"]') and not(matches(., '[‘’“”]')))"
>Straight quotation marks found. Change to curly? [<value-of
select="string-join(analyze-string(normalize-space(.), '\S*[''"]\S*')/fn:match, '; ')"
/>] </assert>
<!-- flag mismatched orientation in pairs of quotes -->
<assert test="not(matches(., '[’”][‘“]|[‘“][’”]'))">Mismatched pair of consecutive curly
quotes: [<value-of
select="string-join(analyze-string(., '([’”][‘“]|[‘“][’”])')/fn:match, '; ')"
/>]. Fix orientation.</assert>
<!-- flag spaces surrounding colons and semi-colons -->
<assert role="warn" test="not(matches(replace(. ,'(\s+\.){3,}', ''), '^\s+[\.,:;?!>)\]\}]|[a-z]\s+[\.,:;?!>)\]\}]'))">Space preceding
closing punctuation: [<value-of
select="string-join(analyze-string(replace(. ,'(\s+\.){3,}', ''), '^(\s+[\.,:;?!>)\]\}]+)+|\w+(\s+[\.,:;?!>)\]\}]+)+')/fn:match ! ('“' || . || '”'), ', ')"
/>]. Remove space?</assert>
</rule>
</pattern>
<pattern id="quote-checks-block-elements">
<title>Quote checks (on block elements only)</title>
<!-- prevent these block elements from having inverted quote marks in their first & last characters.
note: if we added these tests to all text nodes, we'd get lots of false positives,
e.g., quotes around italicized text or terms.
note: segs and cells sometime contain a double quote (“, ”, or "), i.e., ditto marks.
FRUS has used all 3 forms in typeset editions.
this check excludes such cases (though the "curly-quote-orientation-checks" above may catch some),
but it flags all others. -->
<rule
context="tei:p | tei:head | tei:note[@rend eq 'inline'] | tei:item[not(tei:list)] | (tei:seg | tei:cell)[not(matches(., '^[“”" ]+$'))]">
<assert test="not(matches(., '[“‘]$'))">Curly open quotation mark appears as final
character of <value-of select="./name()"/> element. Fix orientation or delete?</assert>
<assert test="not(matches(., '^[”’][^\d]'))">Curly close quotation mark appears first
character of this <value-of select="./name()"/> element. Fix orientation or delete?</assert>
</rule>
</pattern>
<pattern id="signed-checks">
<title>Signature block checks</title>
<rule context="tei:signed">
<assert test="not(@corresp)" id="signed-corresp">The @corresp attribute is not allowed on the signed
element; it should be moved to the persName inside the signed element</assert>
<assert test=".//tei:persName[not(ancestor::tei:note)]">Signature blocks must contain a
persName</assert>
</rule>
<rule context="tei:signed//tei:persName[not(ancestor::tei:note)]">
<assert test="empty(.) or tei:hi[@rend eq 'strong']" role="warn">People who signed must be wrapped
in a hi/@rend="strong" element</assert>
<let name="immediate-following-sibling-node"
value="following-sibling::node()[not(name() = ('note', 'lb') or normalize-space(.) eq '')][1]"/>
<assert
test="empty($immediate-following-sibling-node) or (matches(normalize-space($immediate-following-sibling-node), '^\p{P}*$') or $immediate-following-sibling-node/self::tei:hi/@rend eq 'italic')"
>Text following a persName element must be wrapped in a hi/@rend="italic" element,
not an affiliation element; be sure any line breaks are marked with lb
elements</assert>
<let name="following-nodes"
value="following-sibling::node()[not(name(.) = ('note', 'persName', 'affiliation'))]"/>
<assert
test="count($following-nodes) le 1 or ($following-nodes/self::tei:hi/@rend = 'italic' and $following-nodes/self::tei:lb)"
>Any persName elements followed by italicized text should be separated by lb
elements.</assert>
</rule>
<rule context="tei:signed//tei:persName[not(ancestor::tei:note)]">
<assert test="following-sibling::tei:persName" role="warn">This may need to be adapted
to a list/item structure</assert>
</rule>
</pattern>
<pattern id="image-url-checks">
<title>Image Checks</title>
<rule context="tei:graphic">
<assert test="@url">Missing @url attribute</assert>
<assert test="not(matches(@url, '\..{3,4}$'))">File extensions not allowed: <value-of
select="@url"/></assert>
</rule>
</pattern>
<pattern id="image-s3-checks">
<title>Image Checks</title>
<rule context="tei:graphic[@url][not(ancestor::tei:titlePage)]">
<assert test="concat(@url, '.png') = $available-images">PNG version of '<value-of
select="@url"/>' not found on static.history.state.gov</assert>
<assert test="concat(@url, '.tif') = $available-images">TIFF version of '<value-of
select="@url"/>' not found on static.history.state.gov</assert>
</rule>
</pattern>
<!-- XSL Helper Functions -->
<!-- Function to convert from Roman Numerals to Numbers. Adapted from Sal Mangano, XSLT Cookbook 2nd Ed (O'Reilly 2006), pp. 70-72 -->
<ckbk:romans>
<ckbk:roman value="1">i</ckbk:roman>
<ckbk:roman value="1">I</ckbk:roman>
<ckbk:roman value="5">v</ckbk:roman>
<ckbk:roman value="5">V</ckbk:roman>
<ckbk:roman value="10">x</ckbk:roman>
<ckbk:roman value="10">X</ckbk:roman>
<ckbk:roman value="50">l</ckbk:roman>
<ckbk:roman value="50">L</ckbk:roman>
<ckbk:roman value="100">c</ckbk:roman>
<ckbk:roman value="100">C</ckbk:roman>
<ckbk:roman value="500">d</ckbk:roman>
<ckbk:roman value="500">D</ckbk:roman>
<ckbk:roman value="1000">m</ckbk:roman>
<ckbk:roman value="1000">M</ckbk:roman>
</ckbk:romans>
<xsl:variable name="ckbk:roman-nums" select="document('')/*/*/ckbk:roman"/>
<xsl:function name="ckbk:roman-to-number">
<xsl:param name="roman"/>
<xsl:variable name="valid-roman-chars">
<xsl:value-of select="document('')/*/ckbk:romans"/>
</xsl:variable>
<xsl:choose>
<!-- returns true if there are any non-Roman characters in the string -->
<xsl:when test="translate($roman, $valid-roman-chars, '')">NaN</xsl:when>
<xsl:otherwise>
<xsl:call-template name="ckbk:roman-to-number-impl">
<xsl:with-param name="roman" select="$roman"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template name="ckbk:roman-to-number-impl">
<xsl:param name="roman"/>
<xsl:param name="value" select="0"/>
<xsl:variable name="len" select="string-length($roman)"/>
<xsl:choose>
<xsl:when test="not($len)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:when test="$len = 1">
<xsl:value-of select="$value + $ckbk:roman-nums[. = $roman]/@value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="roman-num"
select="$ckbk:roman-nums[. = substring($roman, 1, 1)]"/>
<xsl:choose>
<xsl:when
test="$roman-num/following-sibling::ckbk:roman = substring($roman, 2, 1)">
<xsl:call-template name="ckbk:roman-to-number-impl">
<xsl:with-param name="roman" select="substring($roman, 2, $len - 1)"/>
<xsl:with-param name="value" select="$value - $roman-num/@value"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="ckbk:roman-to-number-impl">
<xsl:with-param name="roman" select="substring($roman, 2, $len - 1)"/>
<xsl:with-param name="value" select="$value + $roman-num/@value"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</schema>