+
0">
+ {{ 'analogue' | translate }}:
+ {{ 'source' | translate }}:
diff --git a/src/app/components/note/note.component.scss b/src/app/components/note/note.component.scss
index 7aa07ffb2..237d1b13a 100644
--- a/src/app/components/note/note.component.scss
+++ b/src/app/components/note/note.component.scss
@@ -74,4 +74,24 @@
}
}
}
+
+ &[data-note-type='analogue'] {
+ .note-icon {
+ background: get-color(analogueNotes);
+
+ .arrow {
+ border-top-color: get-color(criticalNotes);
+ }
+ }
+ }
+
+ &[data-note-type='source'] {
+ .note-icon {
+ background: get-color(sourceNotes);
+
+ .arrow {
+ border-top-color: get-color(criticalNotes);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/app/components/paragraph/paragraph.component.html b/src/app/components/paragraph/paragraph.component.html
index 5af398f3d..9ee011ae6 100644
--- a/src/app/components/paragraph/paragraph.component.html
+++ b/src/app/components/paragraph/paragraph.component.html
@@ -1,5 +1,6 @@
{{data.n}}
+
diff --git a/src/app/components/quote-entry/quote-entry.component.html b/src/app/components/quote-entry/quote-entry.component.html
new file mode 100644
index 000000000..88031b06b
--- /dev/null
+++ b/src/app/components/quote-entry/quote-entry.component.html
@@ -0,0 +1,21 @@
+
+ 0">
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/quote-entry/quote-entry.component.scss b/src/app/components/quote-entry/quote-entry.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/app/components/quote-entry/quote-entry.component.spec.ts b/src/app/components/quote-entry/quote-entry.component.spec.ts
new file mode 100644
index 000000000..0c3058bf3
--- /dev/null
+++ b/src/app/components/quote-entry/quote-entry.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { QuoteEntryComponent } from './quote-entry.component';
+
+describe('QuoteEntryComponent', () => {
+ let component: QuoteEntryComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ QuoteEntryComponent ],
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(QuoteEntryComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/quote-entry/quote-entry.component.ts b/src/app/components/quote-entry/quote-entry.component.ts
new file mode 100644
index 000000000..b4b862b34
--- /dev/null
+++ b/src/app/components/quote-entry/quote-entry.component.ts
@@ -0,0 +1,93 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { BehaviorSubject, Subject } from 'rxjs';
+import { scan, startWith } from 'rxjs/operators';
+
+import { EditorialConventionLayoutData } from '../../directives/editorial-convention-layout.directive';
+
+import { Note, QuoteEntry, SourceClass } from '../../models/evt-models';
+import { register } from '../../services/component-register.service';
+import { EVTStatusService } from '../../services/evt-status.service';
+import { EditionLevelType } from 'src/app/app.config';
+import { EditionlevelSusceptible, Highlightable } from '../components-mixins';
+
+export interface QuoteEntryComponent extends EditionlevelSusceptible, Highlightable {}
+@register(QuoteEntry)
+@Component({
+ selector: 'evt-quote-entry',
+ templateUrl: './quote-entry.component.html',
+ styleUrls: ['./quote-entry.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class QuoteEntryComponent implements OnInit {
+ public _data: QuoteEntry;
+ private edLevel: EditionLevelType;
+ public sourceClass = SourceClass;
+ public dataForNote = {};
+ public opened = false;
+
+ @Input() set data(dt: QuoteEntry) {
+ this._data = dt;
+ }
+
+ @Input() set editionLevel(el: EditionLevelType) {
+ this.edLevel = el;
+ this.editionLevelChange.next(el);
+ }
+
+ get editionLevel() { return this.edLevel; }
+ editionLevelChange = new BehaviorSubject('');
+
+ get editorialConventionData(): EditorialConventionLayoutData {
+ return {
+ name: 'sources',
+ attributes: this.data?.attributes || {},
+ editionLevel: this.editionLevel,
+ defaultsKey: 'sources',
+ };
+ }
+
+ get data() { return this._data; }
+
+ toggleOpened$ = new Subject();
+
+ opened$ = this.toggleOpened$.pipe(
+ scan((currentState: boolean, val: boolean | undefined) => val === undefined ? !currentState : val, false),
+ startWith(false),
+ );
+
+ toggleAppEntryBox(e: MouseEvent) {
+ e.stopPropagation();
+ this.opened = !this.opened;
+ }
+
+ closeAppEntryBox() {
+ this.opened = false;
+ }
+
+ /** If quote has no text it's displayed as a note.*/
+ createNote(v): Note|{} {
+ return {
+ type: Note,
+ noteType: 'source',
+ noteLayout: 'popover',
+ exponent: v.path || '',
+ content: v.extSources.concat(v.extElements),
+ attributes: v.attributes || [],
+ }
+ }
+
+ stopPropagation(e: MouseEvent) {
+ e.stopPropagation();
+ }
+
+ constructor(
+ public evtStatusService: EVTStatusService,
+ ) {}
+
+ ngOnInit() {
+ if ((this.data.isNoteView) || ((this.data.text.length === 0) && ((this.data.extElements.length !== 0) || (this.data.extSources.length !== 0)))) {
+ this.dataForNote = this.createNote(this.data);
+ }
+ }
+
+}
diff --git a/src/app/components/sources/source-detail/source-detail.component.html b/src/app/components/sources/source-detail/source-detail.component.html
new file mode 100644
index 000000000..6d56fe61f
--- /dev/null
+++ b/src/app/components/sources/source-detail/source-detail.component.html
@@ -0,0 +1,111 @@
+
+
+
0">
+
+ ❝
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/sources/source-detail/source-detail.component.spec.ts b/src/app/components/sources/source-detail/source-detail.component.spec.ts
new file mode 100644
index 000000000..19c8d10fe
--- /dev/null
+++ b/src/app/components/sources/source-detail/source-detail.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SourceDetailComponent } from './source-detail.component';
+
+describe('SourceDetailComponent', () => {
+ let component: SourceDetailComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ SourceDetailComponent ],
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SourceDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/sources/source-detail/source-detail.component.ts b/src/app/components/sources/source-detail/source-detail.component.ts
new file mode 100644
index 000000000..c718b1bef
--- /dev/null
+++ b/src/app/components/sources/source-detail/source-detail.component.ts
@@ -0,0 +1,44 @@
+import { Component, Input } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+import { EditionLevelType } from 'src/app/app.config';
+import { QuoteEntry } from 'src/app/models/evt-models';
+
+@Component({
+ selector: 'evt-source-detail',
+ templateUrl: './source-detail.component.html',
+ styleUrls: ['../../sources/sources.component.scss'],
+})
+export class SourceDetailComponent {
+
+ private edLevel: EditionLevelType;
+
+ public sourceEntry: QuoteEntry;
+
+ public headVisible: boolean;
+
+ public detailVisible: boolean;
+
+ @Input() set source(el: QuoteEntry) {
+ this.sourceEntry = el;
+ this.checkVisibility(el);
+ }
+ get source() { return this.sourceEntry; }
+
+ @Input() set editionLevel(el: EditionLevelType) {
+ this.edLevel = el;
+ this.editionLevelChange.next(el);
+ }
+ get editionLevel() { return this.edLevel; }
+ editionLevelChange = new BehaviorSubject('');
+
+ checkVisibility(source: QuoteEntry) {
+ this.headVisible = (source.sources.length > 0 || source.extSources.length > 0 || source.text.length > 0);
+ this.detailVisible = (source.sources.length > 0 || source.extSources.length > 0 || source.extElements.length > 0);
+ }
+
+ stopPropagation(e: MouseEvent) {
+ e.stopPropagation();
+ }
+
+}
+
diff --git a/src/app/components/sources/source-note/source-note.component.html b/src/app/components/sources/source-note/source-note.component.html
new file mode 100644
index 000000000..7423eb0d4
--- /dev/null
+++ b/src/app/components/sources/source-note/source-note.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/components/sources/source-note/source-note.component.spec.ts b/src/app/components/sources/source-note/source-note.component.spec.ts
new file mode 100644
index 000000000..7ffee2fe7
--- /dev/null
+++ b/src/app/components/sources/source-note/source-note.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SourceNoteComponent } from './source-note.component';
+
+describe('SourceNoteComponent', () => {
+ let component: SourceNoteComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ SourceNoteComponent ],
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SourceNoteComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/sources/source-note/source-note.component.ts b/src/app/components/sources/source-note/source-note.component.ts
new file mode 100644
index 000000000..d53dfda40
--- /dev/null
+++ b/src/app/components/sources/source-note/source-note.component.ts
@@ -0,0 +1,49 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Note, Paragraph, Verse, VersesGroup } from 'src/app/models/evt-models';
+
+@Component({
+ selector: 'evt-source-note',
+ templateUrl: './source-note.component.html',
+ styleUrls: ['../../sources/sources.component.scss'],
+})
+export class SourceNoteComponent implements OnInit {
+ public _data: Paragraph|Verse|VersesGroup;
+ public dataForNote = {};
+
+ @Input() set data(dt: Paragraph|Verse|VersesGroup) {
+ this._data = dt;
+ }
+
+ get data() { return this._data }
+
+ createNote(v, type): Note {
+ const item = v[type];
+ let content = item.extSources || [];
+ if (type === 'analogue') {
+ content.push( item.extLinkedElements );
+ content.push( item.sources );
+ }
+ if (type === 'source') {
+ content.push( item.extElements );
+ }
+
+ return {
+ type: Note,
+ noteType: type,
+ noteLayout: 'popover',
+ exponent: v.path || '',
+ content: content,
+ attributes: v.attributes || [],
+ }
+ }
+
+ ngOnInit() {
+ if (this.data.source !== null) {
+ this.dataForNote = this.createNote(this.data, 'source');
+ }
+ if (this.data.analogue !== null) {
+ this.dataForNote = this.createNote(this.data, 'analogue');
+ }
+ }
+
+}
diff --git a/src/app/components/sources/sources.component.html b/src/app/components/sources/sources.component.html
new file mode 100644
index 000000000..0a5d03555
--- /dev/null
+++ b/src/app/components/sources/sources.component.html
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/app/components/sources/sources.component.scss b/src/app/components/sources/sources.component.scss
new file mode 100644
index 000000000..4e542789b
--- /dev/null
+++ b/src/app/components/sources/sources.component.scss
@@ -0,0 +1,144 @@
+@import "../../../assets/scss/themes";
+
+.source-container {
+ border: 1px solid rgba(128, 128, 128, 0.149);
+ padding: 0;
+ margin: 0.5rem 0.5rem 1rem 0.5rem;
+ font-family: serif;
+ box-shadow: 1px 1px 1px rgba(0,0,0,0.1);
+ background-color:rgba(32, 66, 81, 0.18);
+}
+
+.source-detail-content {
+ display: inline-flex !important;
+ background-color: unset !important;
+ padding-bottom: 0.6em !important;
+ pointer-events: none;
+}
+
+.source-quote-button {
+ float: right;
+ margin-right: 0.3rem;
+ font-size: 1.5em;
+ margin-top: -0.5em;
+ color: #00000073;
+ height: 2em;
+ display: none;
+ pointer-events: none;
+}
+
+.bibl-head {
+ margin-left: 2rem;
+}
+
+.source-quote-symbol {
+ font-size: 1rem;
+ color: #263238;
+ margin: 0;
+ padding: 0 0.3rem 0 0;
+ display: inline;
+ font-family: 'evt-icons' !important;
+ font-style: normal;
+ font-feature-settings: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 0;
+ -webkit-font-smoothing: antialiased;
+ vertical-align: super;
+}
+
+.sources-cat {
+ font-variant: small-caps;
+ font-size: 95%;
+ position: relative;
+ font-style: normal;
+}
+
+.source-detail-btn {
+ background-color: transparent;
+ color: #000000;
+ line-height: 1;
+ padding: 0.25rem 0.375rem;
+ cursor: pointer;
+ border-radius: 0;
+ font-style: normal;
+}
+
+.app-detail-content,
+.app-detail-tabs {
+ background-color: #f1f4f5;
+ font-size: 1rem;
+}
+
+.app-detail-content {
+ display: flex;
+ font-style: italic;
+ justify-content: space-between;
+ padding: 0.313rem;
+ background-color: rgba(128, 128, 128, 0.18);
+}
+
+.app-detail-tabs {
+ font-size: 1rem;
+ margin: 0;
+ padding: 0.313rem 0 0 0;
+ .nav-link {
+ background-color: transparent;
+ color: #000000;
+ line-height: 1;
+ padding: 0.25rem 0.375rem;
+ cursor: pointer;
+ border-radius: 0;
+ &.active {
+ color: #000000;
+ }
+ }
+ .nav-pills,
+ .tab-content {
+ margin-right: 0rem;
+ margin-left: 0rem;
+ }
+ .nav-link.active,
+ .tab-content {
+ @include themify($themes) {
+ background-color: themed("appEntryBoxActiveTabBg");
+ }
+ }
+ .tab-content {
+ max-height: 12.5rem;
+ overflow: auto;
+ padding-top: 0.5rem;
+ padding-left: 1rem;
+ padding-bottom: 0.5rem;
+ .info-lemma-wrapper {
+ @include themify($themes) {
+ border-bottom: 1px solid themed("baseBorder");
+ }
+ padding-bottom: 0.438rem;
+ margin-bottom: 0.625rem;
+ }
+ .info-rdg {
+ font-style: italic;
+ font-weight: 600;
+ font-size: 1.063rem;
+ }
+ .info-label {
+ font-size: 0.813rem;
+ text-transform: uppercase;
+ font-weight: 600;
+ }
+ .more-info-label {
+ display: block;
+ font-size: 0.813rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ margin-bottom: 0.25rem;
+ }
+ pre {
+ white-space: pre-wrap;
+ font-size: 75%;
+ margin-bottom: 0;
+ margin-top: -1rem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/sources/sources.component.spec.ts b/src/app/components/sources/sources.component.spec.ts
new file mode 100644
index 000000000..baf51b0d4
--- /dev/null
+++ b/src/app/components/sources/sources.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SourcesComponent } from './sources.component';
+
+describe('SourcesComponent', () => {
+ let component: SourcesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ SourcesComponent ],
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SourcesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/sources/sources.component.ts b/src/app/components/sources/sources.component.ts
new file mode 100644
index 000000000..10a9058b2
--- /dev/null
+++ b/src/app/components/sources/sources.component.ts
@@ -0,0 +1,45 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { EVTStatusService } from 'src/app/services/evt-status.service';
+import { SourceClass } from '../../models/evt-models';
+import { BehaviorSubject, distinctUntilChanged } from 'rxjs';
+import { EditionLevelType } from 'src/app/app.config';
+
+@Component({
+ selector: 'evt-sources',
+ templateUrl: './sources.component.html',
+ styleUrls: ['./sources.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SourcesComponent implements OnInit {
+ private edLevel: EditionLevelType;
+ public entries;
+ private appClasses = [SourceClass];
+ public quotesInCurrentPage = this.evtStatusService.getPageElementsByClassList(this.appClasses)
+
+ @Input() pageID : string;
+
+ @Input() set editionLevel(el: EditionLevelType) {
+ this.edLevel = el;
+ this.editionLevelChange.next(el);
+ }
+
+ get editionLevel() { return this.edLevel; }
+ editionLevelChange = new BehaviorSubject('');
+
+ public getEntries(data) {
+ this.entries = data.flat();
+ }
+
+ stopPropagation(e: MouseEvent) {
+ e.stopPropagation();
+ }
+
+ constructor(
+ public evtStatusService: EVTStatusService,
+ ) {}
+
+ ngOnInit() {
+ this.quotesInCurrentPage.pipe(distinctUntilChanged()).subscribe({ next: (data) => { this.getEntries(data) } });
+ }
+
+}
diff --git a/src/app/components/verse/verse.component.html b/src/app/components/verse/verse.component.html
index 97693dbe1..fece0b98b 100644
--- a/src/app/components/verse/verse.component.html
+++ b/src/app/components/verse/verse.component.html
@@ -1,8 +1,8 @@
-
{{data.n}}
-
-
+
\ No newline at end of file
diff --git a/src/app/components/verses-group/verses-group.component.html b/src/app/components/verses-group/verses-group.component.html
index 9682c8c87..0f5d4c63b 100644
--- a/src/app/components/verses-group/verses-group.component.html
+++ b/src/app/components/verses-group/verses-group.component.html
@@ -1,6 +1,7 @@
{{data.n}}
+
diff --git a/src/app/models/evt-models.ts b/src/app/models/evt-models.ts
index 4b74e33c2..32cbb46cb 100644
--- a/src/app/models/evt-models.ts
+++ b/src/app/models/evt-models.ts
@@ -170,6 +170,70 @@ export class ApparatusEntry extends GenericElement {
nestedAppsIDs: string[];
}
+export const SourceClass = 'sourceEntry';
+export const AnalogueClass = 'analogueEntry';
+export const BibliographyClass = 'biblioEntry';
+
+export class QuoteEntry extends GenericElement {
+ id: string;
+ tagName: string;
+ text: string;
+ sources: BibliographicEntry[] | BibliographicList[];
+ extSources: BibliographicEntry[];
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ extElements: any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ analogues: any;
+ originalEncoding: OriginalEncodingNodeType;
+ isInsideCit: boolean;
+ quotedText: string[];
+ isNoteView: boolean;
+ contentToShow: Array>
+ //rend: string;
+}
+
+export class BibliographicList extends GenericElement {
+ id: string;
+ head: string[];
+ sources: BibliographicEntry[];
+}
+
+export class BibliographicEntry extends GenericElement {
+ id: string;
+ author: string[];
+ editor: string[];
+ title: string[];
+ date: string[];
+ publisher: string[];
+ pubPlace: string[];
+ citedRange: string[];
+ biblScope: string[];
+ text: string;
+ quotedText: string;
+ isInsideCit: boolean;
+ originalEncoding: OriginalEncodingNodeType;
+}
+
+export class BibliographicStructEntry extends GenericElement {
+ id: string;
+ analytic: BibliographicEntry[];
+ monogrs: BibliographicEntry[];
+ series: BibliographicEntry[];
+ originalEncoding: OriginalEncodingNodeType;
+}
+
+export class Analogue extends GenericElement {
+ id: string;
+ text: string;
+ sources: BibliographicEntry[];
+ extSources: BibliographicEntry[];
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ extLinkedElements: any;
+ quotedElements: [{ id: string, quote: string }];
+ contentToShow: Array>
+ originalEncoding: OriginalEncodingNodeType;
+}
+
export class Reading extends GenericElement {
id: string;
witIDs: string[];
@@ -194,10 +258,14 @@ export class Note extends GenericElement {
noteLayout: NoteLayout;
noteType: string;
exponent: string;
+ source: QuoteEntry;
+ analogue: Analogue;
}
export class Paragraph extends GenericElement {
n: string;
+ source: QuoteEntry;
+ analogue: Analogue;
}
export class Lb extends GenericElement {
@@ -297,11 +365,15 @@ export class Choice extends GenericElement {
export class Verse extends GenericElement {
n: string;
+ source: QuoteEntry;
+ analogue: Analogue;
}
export class VersesGroup extends GenericElement {
n: string;
groupType: string;
+ source: QuoteEntry;
+ analogue: Analogue;
}
export class Supplied extends GenericElement {
@@ -468,13 +540,13 @@ export class MsItemStruct extends GenericElement {
titles: Array>; // TODO: Add specific type when title is handled
rubric: Rubric;
incipit: Incipit;
- quote: Array>; // TODO: Add specific type when quote is handled
+ quote: QuoteEntry;
explicit: Explicit;
finalRubric: FinalRubric;
colophons: Array>; // TODO: Add specific type when colophon is handled
decoNote: DecoNote;
- listBibl: Array>; // TODO: Add specific type when listBibl is handled
- bibl: Array>; // TODO: Add specific type when bibl is handled
+ listBibl: BibliographicList;
+ bibl: BibliographicEntry;
filiation: Filiation[];
noteEl: Note[];
textLangs: Array>; // TODO: Add specific type when textLang is handled
@@ -1060,6 +1132,32 @@ export class Term extends GenericElement {
rend?: string;
}
+export class Milestone extends GenericElement {
+ id?: string;
+ unit?: string;
+ spanText: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ spanElements: any;
+}
+
+export class Anchor extends GenericElement {
+ id?: string;
+}
+
+export class Span extends GenericElement {
+ id?: string;
+ from: string;
+ to: string;
+ includedText: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ includedElements: any;
+}
+
+export class SpanGrp extends GenericElement {
+ id?: string;
+ spans: Span[]
+}
+
export class Keywords extends GenericElement {
scheme?: string;
terms: Term[];
diff --git a/src/app/services/editorial-conventions.service.ts b/src/app/services/editorial-conventions.service.ts
index c47b7ae05..4fce2902a 100644
--- a/src/app/services/editorial-conventions.service.ts
+++ b/src/app/services/editorial-conventions.service.ts
@@ -5,7 +5,7 @@ import { EditorialConvention, EditorialConventionLayouts } from '../models/evt-m
// List of handled editorial convention
export type EditorialConventionDefaults = 'addition' | 'additionAbove' | 'additionBelow' | 'additionInline' | 'additionLeft' | 'additionRight' |
- 'damage' | 'deletion' | 'sicCrux' | 'surplus';
+ 'damage' | 'deletion' | 'sicCrux' | 'surplus' | 'sources' | 'analogues';
@Injectable({
providedIn: 'root',
@@ -125,14 +125,55 @@ export class EditorialConventionsService {
},
},
},
+ 'sources': {
+ diplomatic: {
+ style: {
+ 'font-style': 'italic',
+ 'font-size': '104%',
+ },
+ },
+ interpretative: {
+ style: {
+ 'font-style': 'italic',
+ 'font-size': '104%',
+ },
+ },
+ critical: {
+ style: {
+ 'font-style': 'italic',
+ 'font-size': '104%',
+ },
+ },
+ },
+ 'analogues': {
+ diplomatic: {
+ pre: '🗎',
+ style: {
+ 'text-decoration': 'underline dotted from-font',
+ },
+ },
+ interpretative: {
+ pre: '🗎',
+ style: {
+ 'text-decoration': 'underline dotted from-font',
+ },
+ },
+ critical: {
+ pre: '🗎',
+ style: {
+ 'text-decoration': 'underline dotted from-font',
+ },
+ },
+ },
};
getLayouts(name: string, attributes: AttributesMap, defaultsKey: EditorialConventionDefaults) {
+ const excludedFromAttributeControl = ['sources', 'analogues'];
const defaultKeys = this.defaultLayouts[defaultsKey];
let layouts: Partial = defaultKeys;
const externalLayouts = this._getExternalConfigs().find((c) => c.element === name &&
- (!attributes || Object.keys(attributes).concat(
+ (excludedFromAttributeControl.includes(name) || !attributes || Object.keys(attributes).concat(
Object.keys(c.attributes)).every((k) => attributes[k] === c.attributes[k])))?.layouts ?? undefined;
if (externalLayouts) {
diff --git a/src/app/services/evt-model.service.ts b/src/app/services/evt-model.service.ts
index 58e2e081c..3cb7ea0b3 100644
--- a/src/app/services/evt-model.service.ts
+++ b/src/app/services/evt-model.service.ts
@@ -21,6 +21,8 @@ import { NamedEntitiesParserService } from './xml-parsers/named-entities-parser.
import { PrefatoryMatterParserService } from './xml-parsers/prefatory-matter-parser.service';
import { StructureXmlParserService } from './xml-parsers/structure-xml-parser.service';
import { WitnessesParserService } from './xml-parsers/witnesses-parser.service';
+import { SourceEntriesParserService } from './xml-parsers/source-entries-parser.service';
+import { AnalogueEntriesParserService } from './xml-parsers/analogues-entries-parser.service';
import { AppConfig } from '../app.config';
@Injectable({
@@ -152,6 +154,18 @@ export class EVTModelService {
shareReplay(1),
);
+ //QUOTED SOURCES
+ public readonly sourceEntries$ = this.editionSource$.pipe(
+ map((source) => this.sourceParser.parseSourceEntries(source)),
+ shareReplay(1),
+ );
+
+ // PARALLEL PASSAGES
+ public readonly analogueEntries$ = this.editionSource$.pipe(
+ map((source) => this.analogueParser.parseAnaloguesEntries(source)),
+ shareReplay(1),
+ );
+
// FACSIMILE
public readonly facsimile$ : Observable = this.editionSource$.pipe(
map((source) => this.facsimileParser.parseFacsimile(source)),
@@ -333,10 +347,10 @@ return undefined;
public readonly msDesc$ = this.editionSource$.pipe(
map((source) => this.msDescParser.parseMsDesc(source)),
shareReplay(1),
-);
-
+ );
constructor(
+ private analogueParser: AnalogueEntriesParserService,
private editionDataService: EditionDataService,
private editionStructureParser: StructureXmlParserService,
private namedEntitiesParser: NamedEntitiesParserService,
@@ -347,6 +361,7 @@ return undefined;
private characterDeclarationsParser: CharacterDeclarationsParserService,
private linesVersesParser: LinesVersesParserService,
private msDescParser: MsDescParserService,
+ private sourceParser: SourceEntriesParserService,
) {
}
diff --git a/src/app/services/evt-status.service.ts b/src/app/services/evt-status.service.ts
index 9b96db511..65c019f59 100644
--- a/src/app/services/evt-status.service.ts
+++ b/src/app/services/evt-status.service.ts
@@ -6,6 +6,7 @@ import { distinctUntilChanged, filter, first, map, mergeMap, shareReplay, switch
import { AppConfig, EditionLevelType } from '../app.config';
import { Page, ViewMode } from '../models/evt-models';
import { EVTModelService } from './evt-model.service';
+import { deepSearch } from '../utils/dom-utils';
export type URLParamsKeys = 'd' | 'p' | 'el' | 'ws' | 'vs';
export type URLParams = { [T in URLParamsKeys]: string };
@@ -131,6 +132,8 @@ export class EVTStatusService {
public currentNamedEntityId$: BehaviorSubject = new BehaviorSubject(undefined);
+ public currentQuotedId$: BehaviorSubject = new BehaviorSubject(undefined);
+
public syncImageNavBar$: BehaviorSubject = new BehaviorSubject(false);
constructor(
@@ -181,6 +184,17 @@ export class EVTStatusService {
params,
};
}
+
+ getPageElementsByClassList(classList) {
+ const attributesNotIncludedInSearch = ['originalEncoding','type'];
+ const maxEffort = 4000;
+
+ return this.currentStatus$.pipe(
+ map(({ page }) => page.parsedContent),
+ map((pageSubElements) => deepSearch(pageSubElements, 'class', classList, maxEffort, attributesNotIncludedInSearch)),
+ );
+ }
+
}
export interface AppStatus {
diff --git a/src/app/services/xml-parsers/analogue-parser.ts b/src/app/services/xml-parsers/analogue-parser.ts
new file mode 100644
index 000000000..e99ba434c
--- /dev/null
+++ b/src/app/services/xml-parsers/analogue-parser.ts
@@ -0,0 +1,133 @@
+import { AppConfig } from 'src/app/app.config';
+import { parse, ParserRegister, xmlParser } from '.';
+import { Analogue, AnalogueClass, BibliographicEntry, BibliographicList, GenericElement, Milestone, XMLElement } from '../../models/evt-models';
+import { AnchorParser, AttributeParser, GenericElemParser, MilestoneParser } from './basic-parsers';
+import { createParser, getID, parseChildren, Parser } from './parser-models';
+import { chainFirstChildTexts, getExternalElements, normalizeSpaces } from '../../utils/xml-utils';
+import { BibliographyParser } from './bilbliography-parsers';
+import { BasicParser } from './quotes-parser';
+
+@xmlParser('evt-analogue-entry-parser', AnalogueParser)
+export class AnalogueParser extends BasicParser implements Parser {
+ elementParser = createParser(GenericElemParser, parse);
+ attributeParser = createParser(AttributeParser, this.genericParse);
+ biblParser = createParser(BibliographyParser, this.genericParse);
+ milestoneParser = createParser(MilestoneParser, this.genericParse);
+ anchorParser = createParser(AnchorParser, this.genericParse);
+
+ analogueMarker = AppConfig.evtSettings.edition.analogueMarkers;
+ biblAttributeToMatch = AppConfig.evtSettings.edition.externalBibliography.biblAttributeToMatch;
+ elemAttributesToMatch = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ notDisplayedInTextFlow = ['Note', 'BibliographicList', 'BibliographicEntry', 'BibliographicStructEntry',
+ 'Analogue', 'MsDesc'];
+ evtTextComplexElements = ['choice', 'app', 'l', 'quote', 'p', 'lg'];
+ evtInnerTextElements = ['#text', 'reg', 'corr', 'rdg'];
+
+ public parse(analogue: XMLElement): GenericElement|Analogue {
+
+ if (!(this.analogueMarker.includes(analogue.getAttribute('type'))) || (analogue.parentElement.tagName === 'cit')) {
+ // no source/target attribute or inside a cit element: not an analogue to display alone
+ return this.elementParser.parse(analogue)
+ }
+
+ const notableElements = ['div','p','l','lg','note'];
+ const sources = this.buildAnalogueSources(analogue);
+ const content = parseChildren(analogue, this.genericParse);
+
+ return {
+ type: Analogue,
+ id: (notableElements.includes(analogue.tagName)) ? 'EVT-ANALOGUE:' + getID(analogue) : getID(analogue),
+ class: AnalogueClass,
+ attributes: this.attributeParser.parse(analogue),
+ text: normalizeSpaces(chainFirstChildTexts(analogue, this.evtTextComplexElements, this.evtInnerTextElements)),
+ content: content,
+ contentToShow: content.filter((x) => !(this.notDisplayedInTextFlow.includes(x['type'].name))),
+ sources: sources.sources,
+ extSources: sources.extSources,
+ extLinkedElements: sources.extLinkedElements,
+ quotedElements: this.getQuotedTextFromElements(sources.sources.concat(sources.extSources), sources.extLinkedElements),
+ originalEncoding: analogue,
+ };
+ }
+
+ /**
+ * Since elements like ref and seg are not only used for parallel passages,
+ * this function checks if the provided element contains an external link to a bibl element
+ * and returns that elements or a false
+ */
+ private buildAnalogueSources(analogue: XMLElement) {
+ const selectorsAllowed = 'bibl, bibStruct, listBibl, cit, quote, note, seg, div, l, lg, p, milestone, anchor';
+ const elsAllowedForSources = ['bibl','listBibl', 'biblStruct', 'ref', 'cit'];
+ const sources = this.getInsideSources(analogue);
+ const extElements = getExternalElements(analogue, this.elemAttributesToMatch, this.biblAttributeToMatch, selectorsAllowed);
+ const extSources = extElements.map((x) => (elsAllowedForSources.includes(x.tagName)) ? this.biblParser.parse(x) : null).filter((x) => x);
+ const parallelPassages = this.selectAndParseParallelElements(extElements);
+
+ return {
+ 'sources': sources,
+ 'extSources': extSources,
+ 'extLinkedElements': parallelPassages,
+ };
+ }
+
+ /**
+ * Retrieve all Bibliography elements *inside* this analogue element
+ * @param quote XMLElement
+ * @returns array of Bibliography Element or a single Bibliography List element
+ */
+ private getInsideSources(analogue: XMLElement): BibliographicEntry[] {
+ const bibl = ['bibl','listBibl','biblStruct','ref'];
+
+ return Array.from(analogue.children)
+ .map((x: XMLElement) => bibl.includes(x['tagName']) ? this.biblParser.parse(x) : null)
+ .filter((x) => x);
+ }
+
+ /**
+ * Gather and send to parse allowed linked parallel passages
+ */
+ private selectAndParseParallelElements(suspectPPs) {
+ const elemParserAssoc = {
+ l: ParserRegister.get('l'),
+ lg: ParserRegister.get('lg'),
+ p: ParserRegister.get('p'),
+ div: this.elementParser,
+ seg: this.elementParser,
+ anchor: this.anchorParser,
+ milestone: this.milestoneParser,
+ quote: this.elementParser,
+ note: this.elementParser,
+ }
+ const addendum = [];
+ const ppElements = suspectPPs.map((x) => (elemParserAssoc[x['tagName']] !== undefined) ? elemParserAssoc[x['tagName']].parse(x) : null)
+ .filter((x) => x);
+ ppElements.map((x) => (x.type === Milestone) ? addendum.push(x.spanElements) : x );
+
+ return ppElements.concat(addendum.flat());
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private getQuotedTextFromSources(nodes: BibliographicEntry[]): any {
+ let quotesInSources = [];
+ nodes.forEach((el: BibliographicEntry|BibliographicList) => {
+ if (el.type === BibliographicList) {
+ quotesInSources = quotesInSources.concat(this.getQuotedTextFromSources(el['sources']));
+ } else {
+ if (el['quotedText'] !== null) {
+ quotesInSources.push({ id: el.id, quote: el['quotedText'] });
+ }
+ };
+ });
+
+ return quotesInSources;
+ }
+
+ private getQuotedTextFromElements(sources: BibliographicEntry[], elements: XMLElement[]): [{id: string, quote: string}] {
+ let quotesInSources = this.getQuotedTextFromSources(sources);
+ const notDisplayedInText = ['Note','BibliographicList','BibliographicEntry','BibliographicStructEntry','Analogue','MsDesc'];
+ elements.forEach((el: XMLElement) => { if (!notDisplayedInText.includes(el['type'])) { quotesInSources.push( { id: el.id, quote: el })} });
+
+ return quotesInSources;
+ }
+
+}
diff --git a/src/app/services/xml-parsers/analogues-entries-parser.service.ts b/src/app/services/xml-parsers/analogues-entries-parser.service.ts
new file mode 100644
index 000000000..12edecfd9
--- /dev/null
+++ b/src/app/services/xml-parsers/analogues-entries-parser.service.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import { ParserRegister } from '.';
+import { Analogue, AnalogueClass, XMLElement } from '../../models/evt-models';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class AnalogueEntriesParserService {
+ private className = `.${AnalogueClass}`;
+ private parserName = 'evt-analogue-entry-parser';
+
+ public parseAnaloguesEntries(document: XMLElement) {
+ const analogueParser = ParserRegister.get(this.parserName);
+
+ return Array.from(document.querySelectorAll(this.className))
+ .map((analogue) => analogueParser.parse(analogue) as Analogue);
+ }
+
+}
diff --git a/src/app/services/xml-parsers/apparatus-entries-parser.service.ts b/src/app/services/xml-parsers/apparatus-entries-parser.service.ts
index 7a5950001..d02737a44 100644
--- a/src/app/services/xml-parsers/apparatus-entries-parser.service.ts
+++ b/src/app/services/xml-parsers/apparatus-entries-parser.service.ts
@@ -18,7 +18,7 @@ export class ApparatusEntriesParserService {
public getSignificantReadings(apps: ApparatusEntry[]) {
const signRdgs = {};
apps.forEach((app) => {
- signRdgs[app.id] = app.readings.concat(app.lemma).filter((rdg: Reading) => rdg.significant);
+ signRdgs[app.id] = app.readings.concat(app.lemma).filter((rdg: Reading) => rdg?.significant);
});
return signRdgs;
diff --git a/src/app/services/xml-parsers/basic-parsers.ts b/src/app/services/xml-parsers/basic-parsers.ts
index 121a2470a..7f146a0cb 100644
--- a/src/app/services/xml-parsers/basic-parsers.ts
+++ b/src/app/services/xml-parsers/basic-parsers.ts
@@ -1,12 +1,15 @@
import { AttributesMap } from 'ng-dynamic-component';
import { ParserRegister, xmlParser } from '.';
import {
- Addition, Attributes, Damage, Deletion, Gap, GenericElement, Lb, Note, NoteLayout,
- Paragraph, PlacementType, Ptr, Space, Supplied, Term, Text, Verse, VersesGroup, Word, XMLElement,
+ Addition, Analogue, Anchor, Attributes, Damage, Deletion, Gap, GenericElement, Lb, Milestone, Note, NoteLayout,
+ Paragraph, PlacementType, Ptr, Space, QuoteEntry, Span, SpanGrp, Supplied, Term, Text, Verse, VersesGroup, Word, XMLElement,
} from '../../models/evt-models';
-import { isNestedInElem, xpath } from '../../utils/dom-utils';
-import { replaceMultispaces } from '../../utils/xml-utils';
+import { getElementsBetweenTreeNode, isNestedInElem, xpath } from '../../utils/dom-utils';
+import { getExternalElements, isAnalogue, isSource, replaceMultispaces } from '../../utils/xml-utils';
import { createParser, getClass, getDefaultN, getID, parseChildren, ParseFn, Parser } from './parser-models';
+import { AppConfig } from 'src/app/app.config';
+import { AnalogueParser } from './analogue-parser';
+import { QuoteParser } from './quotes-parser';
export class EmptyParser {
genericParse: ParseFn;
@@ -53,6 +56,15 @@ export class GenericParser extends GenericElemParser {
protected genericElemParser = createParser(GenericElemParser, this.genericParse);
}
+export class DisambiguationParser extends GenericElemParser {
+ protected elementParser = createParser(GenericElemParser, this.genericParse);
+ protected attributeParser = createParser(AttributeParser, this.genericParse);
+ protected analogueParser = createParser(AnalogueParser, this.genericParse);
+ protected quoteParser = createParser(QuoteParser, this.genericParse);
+ protected sourceAttr = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ protected analogueMarkers = AppConfig.evtSettings.edition.analogueMarkers;
+}
+
@xmlParser('evt-attribute-parser', AttributeParser)
export class AttributeParser extends EmptyParser implements Parser {
parse(data: HTMLElement): Attributes {
@@ -87,13 +99,30 @@ export class TextParser implements Parser {
@xmlParser('p', ParagraphParser)
export class ParagraphParser extends EmptyParser implements Parser {
+ analogueParser = createParser(AnalogueParser, this.genericParse);
+ quoteParser = createParser(QuoteParser, this.genericParse);
+ sourceAttr = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ analogueMarkers = AppConfig.evtSettings.edition.analogueMarkers;
+ source = null;
+ analogue = null;
+
parse(xml: XMLElement): Paragraph {
+
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ this.analogue = this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ this.source = this.quoteParser.parse(xml);
+ }
+
const attributes = ParserRegister.get('evt-attribute-parser').parse(xml) as Attributes;
const paragraphComponent: Paragraph = {
type: Paragraph,
content: parseChildren(xml, this.genericParse),
attributes,
n: getDefaultN(attributes.n),
+ source: this.source,
+ analogue: this.analogue,
};
return paragraphComponent;
@@ -137,6 +166,13 @@ export class SpaceParser extends EmptyParser implements Parser {
@xmlParser('note', NoteParser)
export class NoteParser extends EmptyParser implements Parser {
attributeParser = createParser(AttributeParser, this.genericParse);
+ analogueParser = createParser(AnalogueParser, this.genericParse);
+ quoteParser = createParser(QuoteParser, this.genericParse);
+ sourceAttr = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ analogueMarkers = AppConfig.evtSettings.edition.analogueMarkers;
+ source = null;
+ analogue = null;
+
parse(xml: XMLElement): Note {
const noteLayout: NoteLayout = this.isFooterNote(xml) || this.isNamedEntityNote(xml)
|| ['person', 'place', 'app', 'msDesc'].some((v) => isNestedInElem(xml, v))
@@ -147,6 +183,14 @@ export class NoteParser extends EmptyParser implements Parser {
? 'critical'
: 'comment';
+
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ this.analogue = this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ this.source = this.quoteParser.parse(xml);
+ }
+
const attributes = this.attributeParser.parse(xml);
const noteElement = {
type: Note,
@@ -155,6 +199,8 @@ export class NoteParser extends EmptyParser implements Parser {
exponent: attributes.n,
path: xpath(xml),
content: parseChildren(xml, this.genericParse),
+ source: this.source,
+ analogue: this.analogue,
attributes,
};
@@ -169,7 +215,18 @@ export class NoteParser extends EmptyParser implements Parser {
export class PtrParser extends GenericElemParser implements Parser {
noteParser = createParser(NoteParser, this.genericParse);
elementParser = createParser(GenericElemParser, this.genericParse);
- parse(xml: XMLElement): Ptr | Note | GenericElement {
+ analogueParser = createParser(AnalogueParser, this.genericParse);
+ quoteParser = createParser(QuoteParser, this.genericParse);
+ sourceAttr = AppConfig.evtSettings.edition.externalBibliography.biblAttributeToMatch
+ ptrAttrs = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+
+ parse(xml: XMLElement): Ptr | QuoteEntry | Analogue {
+
+ if (this.isAnalogue(xml)) {
+ return this.analogueParser.parse(xml);
+ }
+
+ // note
if (xml.getAttribute('type') === 'noteAnchor' && xml.getAttribute('target')) {
const noteId = xml.getAttribute('target').replace('#', '');
const rootNode = xml.closest('TEI');
@@ -178,6 +235,10 @@ export class PtrParser extends GenericElemParser implements Parser {
return noteEl ? this.noteParser.parse(noteEl) : this.elementParser.parse(xml);
}
+ if (this.isSource(xml)) {
+ return this.quoteParser.parse(xml);
+ }
+
return {
...super.parse(xml),
type: Ptr,
@@ -188,18 +249,44 @@ export class PtrParser extends GenericElemParser implements Parser {
rend: xml.getAttribute('rend'),
};
}
+
+ private isAnalogue(xml: XMLElement) {
+
+ return (AppConfig.evtSettings.edition.analogueMarkers.includes(xml.getAttribute('type')))
+ };
+
+ private isSource(xml: XMLElement) {
+
+ return ((getExternalElements(xml, this.ptrAttrs, this.sourceAttr, '*')).length !== 0)
+ }
}
@xmlParser('l', VerseParser)
export class VerseParser extends EmptyParser implements Parser {
attributeParser = createParser(AttributeParser, this.genericParse);
+ analogueParser = createParser(AnalogueParser, this.genericParse);
+ quoteParser = createParser(QuoteParser, this.genericParse);
+ sourceAttr = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ analogueMarkers = AppConfig.evtSettings.edition.analogueMarkers;
+ source = null;
+ analogue = null;
parse(xml: XMLElement): Verse {
+
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ this.analogue = this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ this.source = this.quoteParser.parse(xml);
+ }
+
const attributes = this.attributeParser.parse(xml);
const lineComponent: Verse = {
type: Verse,
content: parseChildren(xml, this.genericParse),
attributes,
n: getDefaultN(attributes.n),
+ source: this.source,
+ analogue: this.analogue,
};
return lineComponent;
@@ -209,7 +296,21 @@ export class VerseParser extends EmptyParser implements Parser {
@xmlParser('lg', VersesGroupParser)
export class VersesGroupParser extends EmptyParser implements Parser {
attributeParser = createParser(AttributeParser, this.genericParse);
+ analogueParser = createParser(AnalogueParser, this.genericParse);
+ quoteParser = createParser(QuoteParser, this.genericParse);
+ sourceAttr = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ analogueMarkers = AppConfig.evtSettings.edition.analogueMarkers;
+ source = null;
+ analogue = null;
parse(xml: XMLElement): VersesGroup {
+
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ this.analogue = this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ this.source = this.quoteParser.parse(xml);
+ }
+
const attributes = this.attributeParser.parse(xml);
const lgComponent: VersesGroup = {
type: VersesGroup,
@@ -218,6 +319,8 @@ export class VersesGroupParser extends EmptyParser implements Parser
attributes,
n: getDefaultN(attributes.n),
groupType: getDefaultN(attributes.type),
+ source: this.source,
+ analogue: this.analogue,
};
return lgComponent;
@@ -344,3 +447,137 @@ export class TermParser extends GenericElemParser implements Parser
};
}
}
+
+@xmlParser('milestone', MilestoneParser)
+export class MilestoneParser extends GenericElemParser implements Parser {
+ parse(xml: XMLElement): Milestone {
+
+ const endElement = (xml.getAttribute('spanTo')) ? getExternalElements(xml, ['spanTo'], 'xml:id', 'anchor') : [];
+ const includedElements = (endElement.length !== 0) ? getElementsBetweenTreeNode(xml, endElement[0]) : [];
+ const parsedElements = (includedElements.length !== 0) ?
+ includedElements.map((x: XMLElement) => (x.nodeType !== 3 && x.nodeType !== 8) ? super.parse(x) : x) : [];
+
+ return {
+ type: Milestone,
+ id: xml.getAttribute('xml:id'),
+ attributes: this.attributeParser.parse(xml),
+ unit: xml.getAttribute('unit'),
+ spanText: '',
+ spanElements: parsedElements,
+ content: parseChildren(xml, this.genericParse),
+ };
+ }
+}
+
+@xmlParser('anchor', AnchorParser)
+export class AnchorParser extends GenericElemParser implements Parser {
+ attributeParser = createParser(AttributeParser, this.genericParse);
+ //todo: check if a span is referring to this element's @xml:id?
+ parse(xml: XMLElement): Anchor {
+
+ return {
+ type: Anchor,
+ id: xml.getAttribute('xml:id'),
+ attributes: this.attributeParser.parse(xml),
+ content: parseChildren(xml, this.genericParse),
+ };
+ }
+}
+
+
+@xmlParser('spanGrp', SpanParser)
+@xmlParser('span', SpanParser)
+export class SpanParser extends GenericElemParser implements Parser {
+ attributeParser = createParser(AttributeParser, this.genericParse);
+
+ parse(xml: XMLElement): Span|SpanGrp {
+ if (xml.tagName === 'spanGrp') {
+
+ return {
+ type: SpanGrp,
+ id: xml.getAttribute('xml:id'),
+ attributes: this.attributeParser.parse(xml),
+ spans: Array.from(xml.querySelectorAll('span')).map((x) => this.parse(x)),
+ content: parseChildren(xml, this.genericParse),
+ }
+
+ }
+ if (xml.tagName === 'span') {
+ const endElement = (xml.getAttribute('spanTo')) ? getExternalElements(xml, ['from'], 'xml:id', 'anchor') : [];
+ const includedElements = (endElement.length !== 0) ? getElementsBetweenTreeNode(xml, endElement[0]) : [];
+ const parsedElements = (includedElements.length !== 0) ?
+ includedElements.map((x: XMLElement) => (x.nodeType !== 3 && x.nodeType !== 8) ? super.parse(x) : x) : [];
+
+ return {
+ type: Span,
+ id: xml.getAttribute('xml:id'),
+ attributes: this.attributeParser.parse(xml),
+ from: xml.getAttribute('from'),
+ to: xml.getAttribute('to'),
+ includedText: '',
+ includedElements: parsedElements,
+ content: parseChildren(xml, this.genericParse),
+ };
+ }
+ }
+}
+
+@xmlParser('ref', RefParser)
+export class RefParser extends DisambiguationParser implements Parser {
+ parse(xml: XMLElement): Analogue | QuoteEntry | GenericElement {
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ return this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ return this.quoteParser.parse(xml);
+ }
+
+ return this.elementParser.parse(xml)
+ }
+}
+
+@xmlParser('seg', SegParser)
+export class SegParser extends DisambiguationParser implements Parser {
+ parse(xml: XMLElement): Analogue | QuoteEntry | GenericElement {
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ return this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ return this.quoteParser.parse(xml);
+ }
+
+ return this.elementParser.parse(xml)
+ }
+}
+
+@xmlParser('div', DivParser)
+export class DivParser extends DisambiguationParser implements Parser {
+ parse(xml: XMLElement): Analogue | QuoteEntry | GenericElement {
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ return this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ return this.quoteParser.parse(xml);
+ }
+
+ return this.elementParser.parse(xml)
+ }
+}
+
+@xmlParser('cit', CitParser)
+export class CitParser extends DisambiguationParser implements Parser {
+ content = [];
+ parse(xml: XMLElement): Analogue | QuoteEntry | GenericElement {
+ if (isAnalogue(xml, this.analogueMarkers)) {
+ return this.analogueParser.parse(xml);
+ }
+ if (isSource(xml, this.sourceAttr)) {
+ return this.quoteParser.parse(xml);
+ }
+
+ const quote = xml.querySelector('quote');
+ if (quote) {
+ return this.quoteParser.parse(quote);
+ }
+ }
+}
diff --git a/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts b/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts
new file mode 100644
index 000000000..71ea2801c
--- /dev/null
+++ b/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import { ParserRegister } from '.';
+import { BibliographyClass, XMLElement } from '../../models/evt-models';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class BibliographicEntriesParserService {
+ private tagName = `.${BibliographyClass}`;
+ private parserName = 'evt-bibliographic-entry-parser';
+ public parseAnaloguesEntries(document: XMLElement) {
+ const bibliographicParser = ParserRegister.get(this.parserName);
+
+ return Array.from(document.querySelectorAll(this.tagName))
+ .map((bib) => bibliographicParser.parse(bib));
+ }
+
+}
+
diff --git a/src/app/services/xml-parsers/bilbliography-parsers.ts b/src/app/services/xml-parsers/bilbliography-parsers.ts
new file mode 100644
index 000000000..8dd144837
--- /dev/null
+++ b/src/app/services/xml-parsers/bilbliography-parsers.ts
@@ -0,0 +1,97 @@
+import { normalizeSpaces } from 'src/app/utils/xml-utils';
+import { parse, xmlParser } from '.';
+import { BibliographicEntry, BibliographicList, BibliographicStructEntry, BibliographyClass, XMLElement } from '../../models/evt-models';
+import { AttributeParser, GenericElemParser } from './basic-parsers';
+import { createParser, getID, parseChildren, Parser } from './parser-models';
+import { BasicParser } from './quotes-parser';
+
+@xmlParser('listBibl', BibliographyParser)
+@xmlParser('biblStruct', BibliographyParser)
+@xmlParser('bibl', BibliographyParser)
+@xmlParser('evt-bibliographic-entry-parser', BibliographyParser)
+export class BibliographyParser extends BasicParser implements Parser {
+ protected attributeParser = createParser(AttributeParser, this.genericParse);
+ protected elementParser = createParser(GenericElemParser, parse);
+
+ protected getTrimmedText = function(s) {
+ return s.textContent.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
+ }
+
+ protected getChildrenTextByName = function(xml : XMLElement, name : string) {
+ return Array.from(xml.querySelectorAll(name)).map((x) => ' '+this.getTrimmedText(x));
+ }
+
+ protected getChildrenByNameOnFirstLevelOnly = function(xml : XMLElement, name : string) {
+ return Array.from(xml.querySelectorAll(':scope > '+name)).map((x) => this.getTrimmedText(x));
+ }
+
+ protected getChildrenTextAndSpecificAttribute = function(xml: XMLElement, name: string, attribute: string) {
+ return Array.from(xml.querySelectorAll(name)).map((x) => x.getAttribute(attribute)+' '+this.getTrimmedText(x));
+ }
+
+ protected getQuoteElementText(element: XMLElement): string {
+ const target = (element.parentNode['tagName'] === 'cit' || element.parentNode['tagName'] === 'note') ? element.parentNode : element;
+ const quotes = Array.from(target.querySelectorAll('quote'));
+ if (quotes.length !== 0) {
+ return normalizeSpaces(quotes[0].textContent);
+ }
+
+ return null;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ parse(xml: XMLElement): any {
+
+ switch (xml.tagName) {
+ case 'ref':
+ case 'analytic':
+ case 'series':
+ case 'monogr':
+ case 'bibl':
+ return {
+ type: BibliographicEntry,
+ id: getID(xml),
+ class: BibliographyClass,
+ attributes: this.attributeParser.parse(xml),
+ title: this.getChildrenTextByName(xml,'title'),
+ author: this.getChildrenTextByName(xml,'author'),
+ editor: this.getChildrenTextByName(xml,'editor'),
+ date: this.getChildrenTextByName(xml,'date'),
+ publisher: this.getChildrenTextByName(xml,'publisher'),
+ pubPlace: this.getChildrenTextByName(xml,'pubBlace'),
+ citedRange: this.getChildrenTextByName(xml,'citedRange'),
+ biblScope: this.getChildrenTextAndSpecificAttribute(xml, 'biblScope', 'unit'),
+ content: parseChildren(xml, this.genericParse),
+ text: xml.textContent,
+ quotedText: this.getQuoteElementText(xml),
+ isInsideCit: (xml.parentNode['tagName'] === 'cit' || xml.parentNode['tagName'] === 'note'),
+ originalEncoding: xml,
+ };
+ case 'cit':
+ case 'listBibl':
+ case 'note':
+ return {
+ type: BibliographicList,
+ id: getID(xml),
+ attributes: this.attributeParser.parse(xml),
+ head: Array.from(xml.querySelectorAll('head')).map((x) => x.textContent),
+ sources: Array.from(xml.querySelectorAll('bibl')).map((x) => this.parse(x)),
+ content: parseChildren(xml, this.genericParse),
+ };
+ case 'biblStruct':
+ return {
+ type: BibliographicStructEntry,
+ id: getID(xml),
+ attributes: this.attributeParser.parse(xml),
+ analytic: Array.from(xml.querySelectorAll('analytic')).map((x) => this.parse(x)),
+ monogrs: Array.from(xml.querySelectorAll('monogr')).map((x) => this.parse(x)),
+ series: Array.from(xml.querySelectorAll('series')).map((x) => this.parse(x)),
+ content: parseChildren(xml, this.genericParse),
+ originalEncoding: xml,
+ };
+ default:
+ // it should never reach here but we don't want risking to not parse an element anyway...
+ return this.elementParser.parse(xml)
+ }
+ }
+}
diff --git a/src/app/services/xml-parsers/index.ts b/src/app/services/xml-parsers/index.ts
index 058dd84f8..4bd2d22ea 100644
--- a/src/app/services/xml-parsers/index.ts
+++ b/src/app/services/xml-parsers/index.ts
@@ -27,11 +27,14 @@ export class ParserRegister {
if (nels.includes(tagName)) {
return 'evt-named-entities-list-parser';
}
+ const quote = ['quote'];
+ if (quote.includes(tagName)) {
+ return 'evt-quote-entry-parser';
+ }
const crit = ['app'];
if (crit.includes(tagName)) {
return 'evt-apparatus-entry-parser';
}
-
if (!Object.keys(ParserRegister.PARSER_MAP).includes(tagName)) {
return 'evt-generic-elem-parser';
}
diff --git a/src/app/services/xml-parsers/msdesc-parser.ts b/src/app/services/xml-parsers/msdesc-parser.ts
index 68b0eeb41..5e4f562d8 100644
--- a/src/app/services/xml-parsers/msdesc-parser.ts
+++ b/src/app/services/xml-parsers/msdesc-parser.ts
@@ -1,16 +1,17 @@
import { isBoolString } from 'src/app/utils/js-utils';
import { xmlParser } from '.';
import {
- AccMat, Acquisition, Additional, Additions, AdminInfo, AltIdentifier, Binding, BindingDesc, Collation, CollectionEl, Condition,
- CustEvent, CustodialHist, DecoDesc, DecoNote, Depth, Dim, Dimensions, Explicit, Filiation, FinalRubric, Foliation,
+ AccMat, Acquisition, Additional, Additions, AdminInfo, AltIdentifier, BibliographicEntry, Binding, BindingDesc, Collation, CollectionEl,
+ Condition, CustEvent, CustodialHist, DecoDesc, DecoNote, Depth, Dim, Dimensions, Explicit, Filiation, FinalRubric, Foliation,
G, HandDesc, HandNote, Head, Height, History, Identifier, Incipit, Institution, Layout, LayoutDesc, Locus, LocusGrp, MaterialValues,
MsContents, MsDesc, MsFrag, MsIdentifier, MsItem, MsItemStruct, MsName, MsPart, MusicNotation, Note, ObjectDesc, OrigDate,
- Origin, OrigPlace, Paragraph, PhysDesc, Provenance, RecordHist, Repository, Rubric, ScriptDesc, Seal, SealDesc, Source, Summary,
+ Origin, OrigPlace, Paragraph, PhysDesc, Provenance, QuoteEntry, RecordHist, Repository, Rubric, ScriptDesc, Seal, SealDesc, Source, Summary,
Support, SupportDesc, Surrogates, Text, TypeDesc, TypeNote, Width, XMLElement,
} from '../../models/evt-models';
import { GenericElemParser, queryAndParseElement, queryAndParseElements } from './basic-parsers';
import { GParser } from './character-declarations-parser';
import { createParser, getClass, getDefaultN, getID, parseChildren, Parser, unhandledElement } from './parser-models';
+import { BibliographicList } from '../../models/evt-models';
class GAttrParser extends GenericElemParser {
protected gParser = createParser(GParser, this.genericParse);
@@ -694,10 +695,10 @@ export class MsItemStructParser extends GenericElemParser implements Parser(xml, 'bibl'),
respStmt: unhandledElement(xml, 'respStmt', this.genericParse),
- quote: unhandledElement(xml, 'quote', this.genericParse),
- listBibl: unhandledElement(xml, 'listBibl', this.genericParse),
+ quote: queryAndParseElement(xml, 'quote'),
+ listBibl: queryAndParseElement(xml, 'listBibl'),
colophons: unhandledElement(xml, 'colophon', this.genericParse),
rubric: queryAndParseElement(xml, 'rubric'),
incipit: queryAndParseElement(xml, 'incipit'),
diff --git a/src/app/services/xml-parsers/quotes-parser.ts b/src/app/services/xml-parsers/quotes-parser.ts
new file mode 100644
index 000000000..28c6dfe25
--- /dev/null
+++ b/src/app/services/xml-parsers/quotes-parser.ts
@@ -0,0 +1,313 @@
+import { AppConfig } from 'src/app/app.config';
+import { ParserRegister, xmlParser } from '.';
+import { Analogue, BibliographicEntry, BibliographicList, BibliographicStructEntry,
+ GenericElement, Note, Paragraph, Ptr, QuoteEntry, SourceClass, Verse, VersesGroup,
+ XMLElement } from '../../models/evt-models';
+import { AnalogueParser } from './analogue-parser';
+import { createParser, getID, parseChildren, ParseFn, Parser } from './parser-models';
+import { isAnalogue, isSource, normalizeSpaces } from 'src/app/utils/xml-utils';
+import { BibliographyParser } from './bilbliography-parsers';
+import { chainFirstChildTexts } from '../../utils/xml-utils';
+import { GenericElemParser } from './basic-parsers';
+
+export class BasicParser {
+ genericParse: ParseFn;
+ constructor(parseFn: ParseFn) { this.genericParse = parseFn; }
+}
+
+/**
+ * It manages note, cit, ref, seg, div, p, l, lb, ptr elements
+ * sent on redirect from other parsers if detected as 'sources'
+*/
+@xmlParser('quote', QuoteParser)
+@xmlParser('evt-quote-entry-parser', QuoteParser)
+export class QuoteParser extends BasicParser implements Parser {
+ elementParser = createParser(GenericElemParser, this.genericParse);
+ attributeParser = ParserRegister.get('evt-attribute-parser');
+ msDescParser = ParserRegister.get('msDesc');
+ biblParser = createParser(BibliographyParser, this.genericParse);
+ analogueParser = createParser(AnalogueParser, this.genericParse);
+ analogueMarkers = AppConfig.evtSettings.edition.analogueMarkers;
+ extMatch = AppConfig.evtSettings.edition.externalBibliography.biblAttributeToMatch;
+ intAttrsToMatch = AppConfig.evtSettings.edition.externalBibliography.elementAttributesToMatch;
+ exceptionParentElements = AppConfig.evtSettings.edition.sourcesExcludedFromListByParent;
+ elementsAllowedForSources = 'bibl, cit, note, seg'; // bibliography
+ elementsAllowedForLink = 'seg, ref, quote, cit, div'; // nested quote elements
+ notDisplayedInTextFlow = ['Note', 'BibliographicList', 'BibliographicEntry',
+ 'BibliographicStructEntry', 'Analogue', 'MsDesc'];
+ evtTextComplexElements = ['choice', 'app', 'l', 'quote', 'p', 'lg'];
+ evtInnerTextElements = ['#text', 'reg', 'corr', 'rdg'];
+
+ xpathRegex = /\sxpath=[\"\'].*[\"\']/g;
+
+ public parse(quote: XMLElement): QuoteEntry | Note | GenericElement | Analogue {
+ const isExcluded = (quote.parentElement) && (this.exceptionParentElements.includes(quote.parentElement.tagName));
+
+ if (isAnalogue(quote, this.analogueMarkers)) {
+ // the element has the @attribute marker for analogues
+ return this.analogueParser.parse(quote);
+ }
+
+ const notSource = ((!isSource(quote, this.intAttrsToMatch)) || (isExcluded));
+
+ switch(quote.tagName) {
+ case 'p':
+ case 'lg':
+ case 'l':
+ case 'note':
+ // if they are not a source send them to their parse otherwise create a note
+ if (notSource) {
+ return ParserRegister.get(quote.tagName).parse(quote) as Paragraph|VersesGroup|Verse|Note;
+ }
+
+ return this.createNote(quote);
+ case 'div':
+ // if it's not a source create a generic element
+ // if it's a source add a note to the generic element
+ const divElement = ParserRegister.get('evt-generic-elem-parser').parse(quote) as GenericElement;
+ if (notSource) {
+ return divElement;
+ }
+ divElement.content.push(this.createNote(quote));
+
+ return divElement;
+ case 'ptr':
+ // if it's not a source send it to its parse, otherwise it will be parsed here later
+ if (notSource) {
+ return ParserRegister.get(quote.tagName).parse(quote) as Ptr;
+ }
+ break;
+ case 'ref':
+ case 'seg':
+ case 'cit':
+ // if they are not a source create a generic element, otherwise it will be parsed here later
+ if (notSource) {
+ return ParserRegister.get('evt-generic-elem-parser').parse(quote) as GenericElement;
+ }
+ break;
+ case 'quote':
+ // always parse here
+ break;
+ }
+
+ // remains 6 cases:
+ // inside a
+ // alone,
+ //
+ // [
+ // ptr ][
+ // note ][
+
+ const isInCit = ((quote.parentElement !== null) && ((quote.parentElement.tagName === 'cit') || (quote.parentElement.tagName === 'note')));
+ const isCit = ((quote.tagName === 'cit') || (quote.tagName === 'note'));
+ const isDiv = (quote.tagName === 'div');
+ const isQuote = (quote.tagName === 'quote');
+ const isAnalogueCheck = ((isAnalogue(quote, this.analogueMarkers)) || (
+ (quote.parentElement !== null) && (isAnalogue(quote.parentElement, this.analogueMarkers))
+ ));
+ const inside = this.getInsideSources(quote, isInCit);
+ const ext = this.getExternalElemsOnce(this.findExtRef(quote, isInCit), this.intAttrsToMatch, this.extMatch);
+ const content = parseChildren(quote, this.genericParse);
+
+ return {
+ type: QuoteEntry,
+ id: getID(quote),
+ tagName: quote.tagName,
+ attributes: this.attributeParser.parse(quote),
+ text: normalizeSpaces(this.getQuotedTextInside(quote, isCit, isDiv)),
+ sources: inside.sources,
+ extSources: ext.extSources,
+ extElements: ext.extElements,
+ quotedText: this.getQuotedTextFromSources(inside.sources.concat(ext.extSources)),
+ analogues: ext.analogues.concat(inside.analogues),
+ class: ( (!isAnalogueCheck) && (!isCit) && ( (!isInCit) || ((isInCit) && (isQuote)) ) ) ? SourceClass : quote.tagName.toLowerCase(),
+ isInsideCit: isInCit,
+ isNoteView: ((quote.tagName === 'note') || (quote.tagName === 'ptr')) ? true : false,
+ content: content,
+ contentToShow: content.filter((el) => !(this.notDisplayedInTextFlow.includes(el['type'].name))),
+ originalEncoding: this.getXML(quote, isInCit),
+ };
+ }
+
+ /**
+ * Returns first-level text elements inside this quote entry
+ */
+ private getQuotedTextInside(quote: XMLElement, isCit: boolean, isDiv: boolean): string {
+ let outText = '';
+ if ((isCit) || (isDiv)) {
+ const elements = Array.from(quote.querySelectorAll('quote, p, l, lg'));
+ elements.forEach((el) => outText += chainFirstChildTexts(el, this.evtTextComplexElements, this.evtInnerTextElements));
+
+ return outText;
+ }
+
+ return chainFirstChildTexts(quote, this.evtTextComplexElements, this.evtInnerTextElements);
+ }
+
+ /**
+ * Choose proper XML node
+ */
+ private getXML(quote: XMLElement, isInCitElem: boolean): XMLElement {
+ if (isInCitElem) {
+ return quote.parentElement;
+ }
+
+ return quote;
+ }
+
+
+ /**
+ * Retrieve attributes linked to external bibl, listBibl, cit elements
+ */
+ private findExtRef(quote: XMLElement, isInCitElem: boolean): XMLElement {
+ const target = (isInCitElem) ? quote.parentElement : quote;
+ const linkAttr = Array.from(target.querySelectorAll('[' +this.intAttrsToMatch.join('], [')+ ']')).map((x) => x);
+ if (linkAttr.length > 0) {
+ return linkAttr[0];
+ }
+
+ return target;
+ }
+
+ /**
+ * Retrieve and send to the proper parsing all bibliography elements inside the quote element
+ * @param quote XMLElement
+ * @returns array of XML Elements
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private getInsideSources(quote: XMLElement, isInCit: boolean): { sources:any, analogues:any } {
+ const prsRg = {
+ bibl: this.biblParser,
+ listBibl: this.biblParser,
+ biblStruct: this.biblParser,
+ msDesc: this.msDescParser,
+ cit: this.biblParser,
+ ref: this.biblParser,
+ note: this.biblParser,
+ }
+
+ const anlgAttr = AppConfig.evtSettings.edition.analogueMarkers;
+ const target = (isInCit) ? quote.parentElement.children : quote.children;
+ const out = { sources: [], analogues: [] }
+
+ Array.from(target).forEach((element: XMLElement) => {
+ if (prsRg[element['tagName']]) {
+ if (!anlgAttr.includes(element.getAttribute('type'))) {
+ out.sources.push( prsRg[element['tagName']].parse(element) );
+ } else {
+ const analogueParsed = this.parse(element);
+ if (analogueParsed['contentToShow']) {
+ out.analogues.push( analogueParsed['contentToShow'] );
+ }
+ }
+ }
+ });
+
+ return out;
+ }
+
+ /**
+ * Retrieve and send to the proper parsing all elements outside the quote element and linked by their @xml:id
+ */
+ private getExternalElemsOnce(quote: XMLElement, attrSrcNames: string[], attrTrgtName: string) {
+ const out = { extElements: [], extSources: [], analogues: [] };
+ const sourceIDs = attrSrcNames.map((x) => quote.getAttribute(x));
+ const sourcesToFind = sourceIDs.filter((x) => x).map((x) => x.replace('#',''));
+ const anlgAttr = AppConfig.evtSettings.edition.analogueMarkers;
+ const elemParserAssoc = {
+ // bibliographic elements
+ bibl: { extSources: this.biblParser },
+ listBibl: { extSources: this.biblParser },
+ msDesc: { extSources: this.msDescParser },
+ biblStruct: { extSources: this.biblParser },
+
+ note: { extElements: this },
+ // this parser elements
+ quote: { extElements: this },
+ cit: { extElements: this },
+
+ seg: { extSources: this.biblParser, extElements: this },
+ ref: { extSources: this.biblParser, extElements: this },
+ // possibile chained sources/analogues
+ div: { extElements: this },
+ p: { extElements: this },
+ l: { extElements: this },
+ lg: { extElements: this },
+ // generic elements
+ item: { extElements: this.elementParser },
+ }
+
+ if (sourcesToFind.length > 0) {
+ const partial = Array.from(quote.ownerDocument.querySelectorAll(Object.keys(elemParserAssoc).join(',')))
+ .filter((x) => sourcesToFind.includes(x.getAttribute(attrTrgtName)))
+
+ partial.forEach((x: XMLElement) => {
+ if (elemParserAssoc[x['tagName']]) {
+ Object.keys(elemParserAssoc[x['tagName']]).forEach((destination) => {
+ if (anlgAttr.includes(x.getAttribute('type'))) {
+ const analogueParsed = this.parse(x);
+ if (analogueParsed['contentToShow']) {
+ out['analogues'].push( analogueParsed['contentToShow'] )
+ }
+ } else {
+ const relativeParser = elemParserAssoc[x['tagName']][destination];
+ out[destination].push( relativeParser.parse(x) )
+ }
+ })
+ };
+ });
+ }
+
+ return out;
+ }
+
+ /**
+ * Inside 'quotedText' attribute of BibliographicEntry is stored the quoted text
+ * @param nodes BibliographicEntry[]
+ * @returns array of object { id: '', 'quote':'' }
+ */
+ private getQuotedTextFromSources(nodes: BibliographicEntry[]): string[] {
+ let quotesInSources = [];
+
+ nodes.forEach((el: BibliographicEntry | BibliographicList | BibliographicStructEntry) => {
+ if (el.type === BibliographicList) {
+ quotesInSources = quotesInSources.concat(this.getQuotedTextFromSources(el['sources']));
+ } else if (el.type === BibliographicEntry) {
+ if (el['quotedText'] !== null) {
+ quotesInSources.push({ id: el.id, quote: el['quotedText'] });
+ }
+ };
+ });
+
+ return quotesInSources;
+ }
+
+ private createNote(quote: XMLElement): QuoteEntry {
+ // const sources = this.getInsideSources(quote, false);
+ // not parsing inside sources, they will be parsed anyway
+ const isCit = ((quote.tagName === 'cit') || (quote.tagName === 'note'));
+ const isDiv = (quote.tagName === 'div');
+ const ext = this.getExternalElemsOnce(quote, this.intAttrsToMatch, this.extMatch);
+ const content = parseChildren(quote, this.genericParse);
+
+ return {
+ type: QuoteEntry,
+ id: 'EVT-SOURCE:' + getID(quote),
+ tagName: quote.tagName,
+ attributes: this.attributeParser.parse(quote),
+ text: normalizeSpaces(this.getQuotedTextInside(quote, isCit, isDiv)),
+ sources: [],
+ analogues: ext.analogues,
+ extSources: ext.extElements.concat(ext.extSources),
+ extElements: ext.extElements,
+ quotedText: this.getQuotedTextFromSources(ext.extElements.concat(ext.extSources)),
+ class: SourceClass,
+ isInsideCit: false,
+ isNoteView: true,
+ content: content,
+ contentToShow: content.filter((el) => !(this.notDisplayedInTextFlow.includes(el['type'].name))),
+ originalEncoding: this.getXML(quote, false),
+ };
+ }
+
+}
diff --git a/src/app/services/xml-parsers/source-entries-parser.service.ts b/src/app/services/xml-parsers/source-entries-parser.service.ts
new file mode 100644
index 000000000..c75bebf07
--- /dev/null
+++ b/src/app/services/xml-parsers/source-entries-parser.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { ParserRegister } from '.';
+import { QuoteEntry, SourceClass, XMLElement } from '../../models/evt-models';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class SourceEntriesParserService {
+ public parseSourceEntries(document: XMLElement) {
+ const quoteParser = ParserRegister.get('evt-quote-entry-parser');
+
+ return [
+ Array.from(document.querySelectorAll(`.${SourceClass}`))
+ .map((srcEntry) => quoteParser.parse(srcEntry) as QuoteEntry),
+ ];
+ }
+}
diff --git a/src/app/services/xml-parsers/xml-parsers.ts b/src/app/services/xml-parsers/xml-parsers.ts
index 8e1d9d1d4..b5c650eef 100644
--- a/src/app/services/xml-parsers/xml-parsers.ts
+++ b/src/app/services/xml-parsers/xml-parsers.ts
@@ -1,8 +1,8 @@
import { Injectable, Type } from '@angular/core';
import { AppParser, RdgParser } from './app-parser';
import {
- AdditionParser, AttributeMapParser, AttributeParser, DamageParser, DeletionParser, GapParser,
- GenericElemParser, LBParser, NoteParser, ParagraphParser, PtrParser, SuppliedParser,
+ AdditionParser, AnchorParser, AttributeMapParser, AttributeParser, DamageParser, DeletionParser, GapParser,
+ GenericElemParser, LBParser, MilestoneParser, NoteParser, ParagraphParser, PtrParser, SpanParser, SuppliedParser,
TermParser, TextParser, VerseParser, VersesGroupParser, WordParser,
} from './basic-parsers';
import { CharParser, GlyphParser, GParser } from './character-declarations-parser';
@@ -38,6 +38,8 @@ import {
NamedEntitiesListParser, NamedEntityRefParser, OrganizationParser,
PersonGroupParser, PersonParser, PlaceParser, RelationParser,
} from './named-entity-parsers';
+import { QuoteParser } from './quotes-parser';
+import { AnalogueParser } from './analogue-parser';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function ParsersDecl(declarations: Array>) {
@@ -58,6 +60,8 @@ export function ParsersDecl(declarations: Array>) {
AdditionParser,
AdditionsParser,
AdminInfoParser,
+ AnalogueParser,
+ AnchorParser,
AltIdentifierParser,
AppParser,
AttributeMapParser,
@@ -130,6 +134,7 @@ export function ParsersDecl(declarations: Array>) {
ListTransposeParser,
LocusGrpParser,
LocusParser,
+ MilestoneParser,
MsContentsParser,
MsDescParser,
MsFragParser,
@@ -165,6 +170,7 @@ export function ParsersDecl(declarations: Array>) {
PublicationStmtParser,
PunctuationParser,
PurposeParser,
+ QuoteParser,
QuotationParser,
RdgParser,
RecordHistParser,
@@ -188,6 +194,7 @@ export function ParsersDecl(declarations: Array>) {
SicParser,
SourceDescParser,
SourceParser,
+ SpanParser,
StdValsParser,
StyleDefDeclParser,
SummaryParser,
diff --git a/src/app/utils/dom-utils.ts b/src/app/utils/dom-utils.ts
index 83bccd0a3..4580300d7 100644
--- a/src/app/utils/dom-utils.ts
+++ b/src/app/utils/dom-utils.ts
@@ -181,11 +181,6 @@ export function balanceXHTML(XHTMLstring: string): string {
/**
* Get all DOM elements contained between the node elements
- *
- * @param start starting node
- * @param end ending node
- *
- * @returns list of nodes contained between start node and end node
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getElementsBetweenTreeNode(start: any, end: any): XMLElement[] {
@@ -229,3 +224,38 @@ export function getCommonAncestor(node1, node2) {
export function createNsResolver(doc: Document) {
return (prefix: string) => prefix === 'ns' ? doc.documentElement.namespaceURI : undefined;
}
+
+export function updateCSS(rules: Array<[string, string]>) {
+ const thisCSS = document.styleSheets[0];
+ rules.forEach((rule) => {
+ thisCSS.insertRule(`${rule[0]} {${rule[1]}}`, 0);
+ });
+}
+
+/**
+ * This function searches inside every property of an object for the provided attribute
+ * it has one of the provided list of values. It stops after a customizable number of iterations to avoid waste of resources.
+ * The limit counter could be inserted in a config, same as the ignoredProperties
+ */
+export function deepSearch(obj, attrToMatch: string, valuesToMatch, counter: number = 4000, ignoredProperties = []) {
+ let results = [];
+ for (const key in obj) {
+ if (!ignoredProperties.includes(key)) {
+ const value = obj[key];
+ if ((key === attrToMatch) && (valuesToMatch.includes(obj[attrToMatch]))) {
+ results.push(obj);
+ }
+ if (typeof value === 'object' && value !== null) {
+ if (counter > 0) {
+ results = results.concat(deepSearch(value, attrToMatch, valuesToMatch, counter, ignoredProperties));
+ counter = counter - 1;
+ } else {
+ console.log('EVT WARN: element is too deep, not searching further in', obj, value);
+ counter = 4000;
+ }
+ }
+ }
+ }
+
+ return results;
+}
diff --git a/src/app/utils/xml-utils.ts b/src/app/utils/xml-utils.ts
index 96d7722d9..260902d3e 100644
--- a/src/app/utils/xml-utils.ts
+++ b/src/app/utils/xml-utils.ts
@@ -36,3 +36,83 @@ export function replaceNotWordChar(textContent: string) {
export function removeSpaces(textContent: string) {
return textContent.replace(/\s/g, '');
}
+
+/**
+ * It removes excessive spaces, any tabulation, new lines and non-word characters
+ */
+export function normalizeSpaces(textContent: string) {
+ return textContent.replace(/[\s]{2,}|\n|\t|\r/g, ' ').trimStart().trimEnd();
+}
+
+/**
+* Significant text sometimes is split inside two or more text evt-element inside the main one, especially when it contains new line characters.
+* This function returns a string with all the text elements chained
+*/
+export function chainFirstChildTexts(elem: XMLElement, evtTextComplexElements: string[], evtInnerTextElements: string[]): string {
+ if (elem === undefined) {
+
+ return '';
+ };
+
+ const evtTextElements = {
+ '#text': 'nodeValue',
+ 'p': 'textContent',
+ };
+ let result = '';
+ elem.childNodes.forEach((node) => (evtTextElements[node.nodeName] !== undefined) ? result += node[ evtTextElements[node.nodeName] ] : (
+ evtTextComplexElements.includes(node.nodeName) ? result += chainDeepTexts(node, evtInnerTextElements) : '' ))
+
+ return result;
+}
+
+/**
+ * Retrieve and chain textContent of all descendents
+ * @param elem ChildNode
+ * @returns out string
+ */
+export function chainDeepTexts(elem: ChildNode, evtInnerTextElements: string[]): string {
+ const textProperty = 'textContent';
+
+ let result = '';
+ elem.childNodes.forEach((x) => (evtInnerTextElements.includes(x.nodeName)) ? ((x[textProperty] !== null) ? result += x[textProperty] : '') : '')
+
+ return result;
+}
+
+/**
+* Retrieve elements in all the document searching between provided elements types and filtering on attributes base
+* It searches all document for a bibl with the correct xml:id.
+* It would be faster if we knew the id or unique element to search in
+*/
+export function getExternalElements(elem: XMLElement, attrSourceNames: string[], attrTargetName: string, elTypes: string): XMLElement[] {
+ const sourceIDs = attrSourceNames.map((x) => elem.getAttribute(x));
+ const sourcesToFind = sourceIDs.filter((x) => x).map((x) => x.replace('#',''));
+
+ if (sourcesToFind.length === 0) {
+ return [];
+ }
+
+ return Array.from(elem.ownerDocument.querySelectorAll(elTypes))
+ .filter((x) => sourcesToFind.includes(x.getAttribute(attrTargetName)))
+}
+
+/**
+ * If an element's attribute 'type' has one of the provided values then it is considered an analogue
+ */
+export function isAnalogue(elem: XMLElement, markerAttrs: string[]): boolean {
+ return (markerAttrs.includes(elem.getAttribute('type')));
+}
+
+/**
+ * If an element has one of the provided attributes then it is considered a source
+ */
+export function isSource(elem: XMLElement, attrs: string[]): boolean {
+ let isSelectedAttributes = false;
+ attrs.forEach((attr) => {
+ if (elem.getAttribute(attr) !== null)
+ { isSelectedAttributes = true }
+ },
+ );
+
+ return (isSelectedAttributes);
+}
diff --git a/src/app/view-modes/reading-text/reading-text.component.html b/src/app/view-modes/reading-text/reading-text.component.html
index 693762a31..7f5c2757e 100644
--- a/src/app/view-modes/reading-text/reading-text.component.html
+++ b/src/app/view-modes/reading-text/reading-text.component.html
@@ -15,21 +15,40 @@
]
diff --git a/src/app/view-modes/reading-text/reading-text.component.scss b/src/app/view-modes/reading-text/reading-text.component.scss
index 2eeaa2ce0..19201a9de 100644
--- a/src/app/view-modes/reading-text/reading-text.component.scss
+++ b/src/app/view-modes/reading-text/reading-text.component.scss
@@ -17,5 +17,20 @@
.tab-pane {
padding: 1rem;
}
+ .source-container .tab-pane {
+ padding: 0.3rem !important;
+ }
+
+ .nav {
+ font-family: serif;
+ --bs-nav-link-color: white;
+ --bs-nav-link-hover-color: white;
+ }
+
+ .nav-tabs {
+ color: #ECEFF1;
+ background-color: #263238;
+ }
+
}
}
\ No newline at end of file
diff --git a/src/assets/config/edition_config.json b/src/assets/config/edition_config.json
index ef7ee3369..ad8514a0b 100644
--- a/src/assets/config/edition_config.json
+++ b/src/assets/config/edition_config.json
@@ -1,109 +1,142 @@
{
- "editionTitle": "My Digital Edition",
- "badge": "alpha",
- "editionHome": "evt.labcd.unipi.it/",
- "availableEditionLevels": [
- {
- "id": "diplomatic",
- "label": "diplomatic",
- "enable": true
- },
- {
- "id": "interpretative",
- "label": "interpretative",
- "enable": true
- },
- {
- "id": "critical",
- "label": "critical",
- "enable": true
- }
- ],
- "namedEntitiesLists": {
- "persons": {
- "defaultLabel": "Persons",
- "enable": true
- },
- "places": {
- "defaultLabel": "Places",
- "enable": true
- },
- "organizations": {
- "defaultLabel": "Organizations",
- "enable": true
- },
- "relations": {
- "defaultLabel": "Relations",
- "enable": true
- },
- "events": {
- "defaultLabel": "Events",
- "enable": true
- }
- },
- "showLists": true,
- "entitiesSelectItems": [
- {
- "label": "Named Entities",
- "items": [
- {
- "value": "persName",
- "label": "persons",
- "color": "#ffcdd2",
- "enable": true
- },
- {
- "value": "persName[type='episcopus']",
- "label": "bishops",
- "color": "rgb(139, 98, 236)",
- "enable": true
- },
- {
- "value": "placeName,geogName",
- "label": "places",
- "color": "#A5D6A7",
- "enable": true
- },
- {
- "value": "orgName",
- "label": "organizations",
- "color": "#FFB74D",
- "enable": true
- }
- ],
- "enable": true
- },
- {
- "label": "Other",
- "items": [
- {
- "value": "event",
- "label": "events",
- "color": "#fcfc60",
- "enable": true
- },
- {
- "value": "rolename",
- "label": "roles",
- "enable": true
- },
- {
- "value": "measure",
- "label": "measures",
- "enable": true
- }
- ],
- "enable": true
- }
- ],
- "notSignificantVariants": [
- "type=orthographic"
- ],
- "defaultEdition": "diplomatic",
- "defaultViewMode": "collation",
- "proseVersesToggler": true,
- "defaultTextFlow": "prose",
- "verseNumberPrinter": 5,
- "readingColorLight": "rgb(208, 220, 255)",
- "readingColorDark": "rgb(101, 138, 255)"
+ "editionTitle": "My Digital Edition",
+ "badge": "alpha",
+ "editionHome": "evt.labcd.unipi.it/",
+ "availableEditionLevels": [
+ {
+ "id": "diplomatic",
+ "label": "diplomatic",
+ "enable": true
+ },
+ {
+ "id": "interpretative",
+ "label": "interpretative",
+ "enable": true
+ },
+ {
+ "id": "critical",
+ "label": "critical",
+ "enable": true
+ }
+ ],
+ "namedEntitiesLists": {
+ "persons": {
+ "defaultLabel": "Persons",
+ "enable": true
+ },
+ "places": {
+ "defaultLabel": "Places",
+ "enable": true
+ },
+ "organizations": {
+ "defaultLabel": "Organizations",
+ "enable": true
+ },
+ "relations": {
+ "defaultLabel": "Relations",
+ "enable": true
+ },
+ "events": {
+ "defaultLabel": "Events",
+ "enable": true
+ }
+ },
+ "showLists": true,
+ "entitiesSelectItems": [
+ {
+ "label": "Named Entities",
+ "items": [
+ {
+ "value": "persName",
+ "label": "persons",
+ "color": "#ffcdd2",
+ "enable": true
+ },
+ {
+ "value": "persName[type='episcopus']",
+ "label": "bishops",
+ "color": "rgb(139, 98, 236)",
+ "enable": true
+ },
+ {
+ "value": "placeName,geogName",
+ "label": "places",
+ "color": "#A5D6A7",
+ "enable": true
+ },
+ {
+ "value": "orgName",
+ "label": "organizations",
+ "color": "#FFB74D",
+ "enable": true
+ }
+ ],
+ "enable": true
+ },
+ {
+ "label": "Other",
+ "items": [
+ {
+ "value": "event",
+ "label": "events",
+ "color": "#fcfc60",
+ "enable": true
+ },
+ {
+ "value": "rolename",
+ "label": "roles",
+ "enable": true
+ },
+ {
+ "value": "measure",
+ "label": "measures",
+ "enable": true
+ }
+ ],
+ "enable": true
+ }
+ ],
+ "notSignificantVariants": [
+ "type=orthographic"
+ ],
+ "defaultEdition": "diplomatic",
+ "defaultViewMode": "collation",
+ "proseVersesToggler": true,
+ "defaultTextFlow": "prose",
+ "verseNumberPrinter": 5,
+ "readingColorLight": "rgb(208, 220, 255)",
+ "readingColorDark": "rgb(101, 138, 255)",
+ "externalBibliography": {
+ "biblAttributeToMatch": "xml:id",
+ "elementAttributesToMatch": [
+ "target",
+ "source"
+ ]
+ },
+ "biblView": {
+ "propsToShow": [
+ "author",
+ "title",
+ "date",
+ "editor",
+ "publisher",
+ "pubPlace",
+ "citedRange",
+ "biblScope"
+ ],
+ "showAttrNames": false,
+ "showEmptyValues": false,
+ "inline": true,
+ "commaSeparated": true,
+ "showMainElemTextContent": true
+ },
+ "analogueMarkers": [
+ "ParallelPassage",
+ "parallelPassage",
+ "Parallel",
+ "parallel"
+ ],
+ "sourcesExcludedFromListByParent": [
+ "desc"
+ ]
}
\ No newline at end of file
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index d51774e5c..ec14aae7d 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -173,6 +173,18 @@
"omit": "omit.",
"wit": "Wit:",
"xml": "XML",
+ "criticalApparatus":"Critical Apparatus",
+ "sources": "Sources",
+ "analogues": "Analogues",
+ "source": "Source",
+ "analogue": "Analogue",
+ "notes":"Notes",
+ "refBibl": "Bibliographic references",
+ "quotedSources": "Quoted sources",
+ "quotedText": "Text",
+ "references": "References",
+ "analogueIn": "Analogue in",
+ "corresps": "Correspondences",
"addImage": "Add image",
"removeImage": "Remove image"
}
\ No newline at end of file
diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json
index 040b2e8e3..c41fecfc8 100644
--- a/src/assets/i18n/it.json
+++ b/src/assets/i18n/it.json
@@ -1,177 +1,189 @@
{
- "defaultTitle": "Visualizzatore EVT",
- "changeLanguage": "Cambia lingua",
- "languageEn": "Inglese",
- "languageIt": "Italiano",
- "changeTheme": "Cambia tema",
- "themeNeutral": "Neutro",
- "themeModern": "Moderno",
- "themeClassic": "Classico",
- "shortcuts": "Scorciatoie da tastiera",
- "aboutEVT": "Info su EVT",
- "projectInfo": "Informazioni progetto",
- "openLists": "Apri liste",
- "bookmark": "Segnalibro",
- "downloadXML": "Scarica XML",
- "pinboard": "Bacheca appunti",
- "pinboardLoading": "Caricamento appunti...",
- "pinboardEmpty": "Nessun appunto trovato.",
- "filteredPinboardEmpty": "Nessun appunto trovato per i filtri impostati.",
- "filterPins": "Filtra appunti",
- "more": "in più",
- "lists": "Liste",
- "noEntities": "Nessuna entità in questa lista",
- "search": "Cerca",
- "noMatches": "Nessun risultato",
- "entityNotFound": "Entità non trovata",
- "noOccurrences": "Nessuna occorrenza",
- "relations": "Relazioni",
- "selectItems": "Seleziona elementi",
- "persons": "Persone",
- "places": "Luoghi",
- "organizations": "Organizzazioni",
- "events": "Eventi",
- "roles": "Ruoli",
- "measures": "Misure",
- "selectAll": "Seleziona tutto",
- "clearAll": "Pulisci selezione",
- "bishops": "Vescovi",
- "diplomatic": "Diplomatica",
- "interpretative": "Interpretativa",
- "critical": "Critica",
- "line": "linea",
- "lines": "linee",
- "char": "carattere",
- "chars": "caratteri",
- "word": "parola",
- "words": "parole",
- "missingS": "mancante",
- "missingP": "mancanti",
- "omitted": "omissione",
- "prose": "Prosa",
- "verses": "Versi",
- "page": "Pagina {{value}}",
- "front": "Front",
- "mainText": "Testo principale",
- "addVersion": "Aggiungi versione",
- "addWitness": "Aggiungi testimone",
- "collection": "Raccolta",
- "place": "Luogo",
- "msIdentifier": "Identificatore del manoscritto",
- "repository": "Archivio",
- "institution": "Istituzione",
- "msName": "Nome",
- "idno": "Identificatore",
- "altIdentifier": "Identificatore alternativo",
- "msContents": "Contenuto del manoscritto",
- "textLang": "Lingua",
- "author": "Autore",
- "docDate": "Data",
- "title": "Titolo",
- "summary": "Riassunto",
- "items": "Testi",
- "incipit": "Incipit",
- "explicit": "Explicit",
- "colophon": "Colophon",
- "note": "Note",
- "docImprint": "Sigla editoriale",
- "phyDesc": "Descrizione fisica",
- "supportDesc": "Supporto",
- "extent": "Grandezza",
- "layoutDesc": "Layout",
- "handDesc": "Scrittura",
- "decoDesc": "Decorazioni",
- "collation": "Collazione",
- "description": "Descrizione",
- "bindingDesc": "Rilegatura",
- "condition": "Condizione fisica",
- "foliation": "Foliazione",
- "history": "Storia del manoscritto",
- "origin": "Origine",
- "provenance": "Provenienza",
- "acquisition": "Acquisizione",
- "additional": "Ulteriori informazioni",
- "listBibl": "Lista di citazioni",
- "surrogates": "Surrogati",
- "adminInfo": "Informazioni amministrative",
- "recordHist": "Storia registrata",
- "custodialHist": "Storia della conservazione",
- "availability": "Disponibilità",
- "msFrag": "Frammento del manoscritto",
- "altIdentifierLabel": "Identificatore alternativo",
- "msPart": "Parte del manoscritto",
- "origPlace": "Luogo di origine",
- "origDate": "Data di origine",
- "selectMsDesc": "Seleziona MsDesc",
- "thumbnails": "Miniature",
- "firstPage": "Prima pagina",
- "previousPage": "Pagina precedente",
- "nextPage": "Pagina successiva",
- "lastPage": "Ultima pagina",
- "toggleToolbar": "Apri/chiudi barra di navigazione",
- "viscoll": "Viscoll",
- "editors": "Editori",
- "sponsors": "Sponsor",
- "funders": "Finanziatori",
- "principals": "Ricercatori principali",
- "responsibles": "Responsabili",
- "publicationStatement": "Informazioni sulla pubblicazione",
- "publisher": "Editore",
- "distributor": "Distributore",
- "authority": "Autorità",
- "publicationPlace": "Luogo di pubblicazione",
- "address": "Indirizzo",
- "date": "Data",
- "licence": "Licenza",
- "editionStatement": "Informazioni sull'edizione",
- "edition": "Edizione",
- "seriesStatement": "Informazioni sulla serie",
- "notesStatement": "Note",
- "sourceDesc": "Descrizione della fonte",
- "fileDesc": "Descrizione del file",
- "encodingDesc": "Codifica",
- "profileDesc": "Profilo del testo",
- "revisionDesc": "Revisioni",
- "projectDesc": "Descrizione del progetto",
- "samplingDecl": "Dichiarazione di campionatura",
- "editorialDecl": "Dichiarazione sulle pratiche editoriali",
- "tagsDecl": "Dichiarazione sulla marcatura",
- "styleDefDecl": "Dichiarazione sul linguaggio stile",
- "refsDecl": "Dichiarazione sui riferimenti",
- "classDecl": "Dichiarazioni sulla classificazione",
- "geoDecl": "Dichiarazione sulle coordinate geografiche",
- "unitDecl": "Dichiarazione sulle unità di misurazione",
- "schemaSpec": "Specifica dello schema",
- "schemaRef": "Riferimento allo schema",
- "correction": "Principi di correzione",
- "hyphenation": "Principi di sillabazione",
- "interpretation": "Principi di interpretazione",
- "normalization": "Principi di normalizzazione",
- "punctuation": "Principi di punteggiatura",
- "quotation": "Gestione delle virgolette",
- "segmentation": "Principi di segmentazione",
- "stdVals": "Valori standard",
- "scope": "Ambito",
- "selector": "Selettore",
- "scheme": "Schema",
- "rules": "Regole",
- "renditionFirstLineDesc": "lo stile si applica alla prima riga dell'elemento di destinazione",
- "renditionFirstLetterDesc": "lo stile si applica alla prima lettera dell'elemento di destinazione",
- "renditionBeforeDesc": "lo stile dovrebbe essere applicato immediatamente prima del contenuto dell'elemento di destinazione",
- "renditionAfterDesc": "lo stile dovrebbe essere applicato immediatamente dopo il contenuto dell'elemento di destinazione",
- "occurrence": "occorrenza",
- "occurrences": "occorrenze",
- "withId": "con l'identificativo",
- "criticalNotes": "Note critiche",
- "ortographicVariants": "Varianti ortografiche",
- "info": "Info",
- "metadataForLemma": "Metadata per il lemma",
- "metadataFor": "Metadata per",
- "noDataAvailable": "Nessuna informazione disponibile",
- "moreInfoAboutApp": "Maggiori informazioni per l'entrata d'apparato",
- "omit": "omit.",
- "wit": "Wit:",
- "xml": "XML",
- "addImage": "Aggiungi immagine",
- "removeImage": "Rimuovi immagine"
-}
\ No newline at end of file
+ "defaultTitle": "Visualizzatore EVT",
+ "changeLanguage": "Cambia lingua",
+ "languageEn": "Inglese",
+ "languageIt": "Italiano",
+ "changeTheme": "Cambia tema",
+ "themeNeutral": "Neutro",
+ "themeModern": "Moderno",
+ "themeClassic": "Classico",
+ "shortcuts": "Scorciatoie da tastiera",
+ "aboutEVT": "Info su EVT",
+ "projectInfo": "Informazioni progetto",
+ "openLists": "Apri liste",
+ "bookmark": "Segnalibro",
+ "downloadXML": "Scarica XML",
+ "pinboard": "Bacheca appunti",
+ "pinboardLoading": "Caricamento appunti...",
+ "pinboardEmpty": "Nessun appunto trovato.",
+ "filteredPinboardEmpty": "Nessun appunto trovato per i filtri impostati.",
+ "filterPins": "Filtra appunti",
+ "more": "in più",
+ "lists": "Liste",
+ "noEntities": "Nessuna entità in questa lista",
+ "search": "Cerca",
+ "noMatches": "Nessun risultato",
+ "entityNotFound": "Entità non trovata",
+ "noOccurrences": "Nessuna occorrenza",
+ "relations": "Relazioni",
+ "selectItems": "Seleziona elementi",
+ "persons": "Persone",
+ "places": "Luoghi",
+ "organizations": "Organizzazioni",
+ "events": "Eventi",
+ "roles": "Ruoli",
+ "measures": "Misure",
+ "selectAll": "Seleziona tutto",
+ "clearAll": "Pulisci selezione",
+ "bishops": "Vescovi",
+ "diplomatic": "Diplomatica",
+ "interpretative": "Interpretativa",
+ "critical": "Critica",
+ "line": "linea",
+ "lines": "linee",
+ "char": "carattere",
+ "chars": "caratteri",
+ "word": "parola",
+ "words": "parole",
+ "missingS": "mancante",
+ "missingP": "mancanti",
+ "omitted": "omissione",
+ "prose": "Prosa",
+ "verses": "Versi",
+ "page": "Pagina {{value}}",
+ "front": "Front",
+ "mainText": "Testo principale",
+ "addVersion": "Aggiungi versione",
+ "addWitness": "Aggiungi testimone",
+ "collection": "Raccolta",
+ "place": "Luogo",
+ "msIdentifier": "Identificatore del manoscritto",
+ "repository": "Archivio",
+ "institution": "Istituzione",
+ "msName": "Nome",
+ "idno": "Identificatore",
+ "altIdentifier": "Identificatore alternativo",
+ "msContents": "Contenuto del manoscritto",
+ "textLang": "Lingua",
+ "author": "Autore",
+ "docDate": "Data",
+ "title": "Titolo",
+ "summary": "Riassunto",
+ "items": "Testi",
+ "incipit": "Incipit",
+ "explicit": "Explicit",
+ "colophon": "Colophon",
+ "note": "Note",
+ "docImprint": "Sigla editoriale",
+ "phyDesc": "Descrizione fisica",
+ "supportDesc": "Supporto",
+ "extent": "Grandezza",
+ "layoutDesc": "Layout",
+ "handDesc": "Scrittura",
+ "decoDesc": "Decorazioni",
+ "collation": "Collazione",
+ "description": "Descrizione",
+ "bindingDesc": "Rilegatura",
+ "condition": "Condizione fisica",
+ "foliation": "Foliazione",
+ "history": "Storia del manoscritto",
+ "origin": "Origine",
+ "provenance": "Provenienza",
+ "acquisition": "Acquisizione",
+ "additional": "Ulteriori informazioni",
+ "listBibl": "Lista di citazioni",
+ "surrogates": "Surrogati",
+ "adminInfo": "Informazioni amministrative",
+ "recordHist": "Storia registrata",
+ "custodialHist": "Storia della conservazione",
+ "availability": "Disponibilità",
+ "msFrag": "Frammento del manoscritto",
+ "altIdentifierLabel": "Identificatore alternativo",
+ "msPart": "Parte del manoscritto",
+ "origPlace": "Luogo di origine",
+ "origDate": "Data di origine",
+ "selectMsDesc": "Seleziona MsDesc",
+ "thumbnails": "Miniature",
+ "firstPage": "Prima pagina",
+ "previousPage": "Pagina precedente",
+ "nextPage": "Pagina successiva",
+ "lastPage": "Ultima pagina",
+ "toggleToolbar": "Apri/chiudi barra di navigazione",
+ "viscoll": "Viscoll",
+ "editors": "Editori",
+ "sponsors": "Sponsor",
+ "funders": "Finanziatori",
+ "principals": "Ricercatori principali",
+ "responsibles": "Responsabili",
+ "publicationStatement": "Informazioni sulla pubblicazione",
+ "publisher": "Editore",
+ "distributor": "Distributore",
+ "authority": "Autorità",
+ "publicationPlace": "Luogo di pubblicazione",
+ "address": "Indirizzo",
+ "date": "Data",
+ "licence": "Licenza",
+ "editionStatement": "Informazioni sull'edizione",
+ "edition": "Edizione",
+ "seriesStatement": "Informazioni sulla serie",
+ "notesStatement": "Note",
+ "sourceDesc": "Descrizione della fonte",
+ "fileDesc": "Descrizione del file",
+ "encodingDesc": "Codifica",
+ "profileDesc": "Profilo del testo",
+ "revisionDesc": "Revisioni",
+ "projectDesc": "Descrizione del progetto",
+ "samplingDecl": "Dichiarazione di campionatura",
+ "editorialDecl": "Dichiarazione sulle pratiche editoriali",
+ "tagsDecl": "Dichiarazione sulla marcatura",
+ "styleDefDecl": "Dichiarazione sul linguaggio stile",
+ "refsDecl": "Dichiarazione sui riferimenti",
+ "classDecl": "Dichiarazioni sulla classificazione",
+ "geoDecl": "Dichiarazione sulle coordinate geografiche",
+ "unitDecl": "Dichiarazione sulle unità di misurazione",
+ "schemaSpec": "Specifica dello schema",
+ "schemaRef": "Riferimento allo schema",
+ "correction": "Principi di correzione",
+ "hyphenation": "Principi di sillabazione",
+ "interpretation": "Principi di interpretazione",
+ "normalization": "Principi di normalizzazione",
+ "punctuation": "Principi di punteggiatura",
+ "quotation": "Gestione delle virgolette",
+ "segmentation": "Principi di segmentazione",
+ "stdVals": "Valori standard",
+ "scope": "Ambito",
+ "selector": "Selettore",
+ "scheme": "Schema",
+ "rules": "Regole",
+ "renditionFirstLineDesc": "lo stile si applica alla prima riga dell'elemento di destinazione",
+ "renditionFirstLetterDesc": "lo stile si applica alla prima lettera dell'elemento di destinazione",
+ "renditionBeforeDesc": "lo stile dovrebbe essere applicato immediatamente prima del contenuto dell'elemento di destinazione",
+ "renditionAfterDesc": "lo stile dovrebbe essere applicato immediatamente dopo il contenuto dell'elemento di destinazione",
+ "occurrence": "occorrenza",
+ "occurrences": "occorrenze",
+ "withId": "con l'identificativo",
+ "criticalNotes": "Note critiche",
+ "ortographicVariants": "Varianti ortografiche",
+ "info": "Info",
+ "metadataForLemma": "Metadata per il lemma",
+ "metadataFor": "Metadata per",
+ "noDataAvailable": "Nessuna informazione disponibile",
+ "moreInfoAboutApp": "Maggiori informazioni per l'entrata d'apparato",
+ "omit": "omit.",
+ "wit": "Wit:",
+ "xml": "XML",
+ "criticalApparatus": "Apparato Critico",
+ "sources": "Fonti",
+ "analogues": "Passi Paralleli",
+ "source": "Fonte",
+ "analogue": "Passo Parallelo",
+ "notes": "Note Critiche",
+ "refBibl": "Riferimenti Bibliografici",
+ "quotedSources": "Fonti citate",
+ "quotedText": "Testo",
+ "references": "Riferimenti",
+ "analogueIn": "Passo parallelo in",
+ "corresps": "Corrispondenze",
+ "addImage": "Aggiungi immagine",
+ "removeImage": "Rimuovi immagine"
+}
diff --git a/src/assets/scss/_colors.scss b/src/assets/scss/_colors.scss
index 8178afda0..d273742ee 100644
--- a/src/assets/scss/_colors.scss
+++ b/src/assets/scss/_colors.scss
@@ -17,6 +17,8 @@ $editionColors: (
),
commentNotes: rgb(87, 14, 105),
criticalNotes: rgb(24, 70, 155),
+ analogueNotes: rgb(24, 70, 155),
+ sourceNotes: rgb(24, 155, 90),
normalization: #69513a,
normalizationBackground: #fffbf3,
emendation: #934d4d,
diff --git a/src/styles.scss b/src/styles.scss
index e717cf20a..3afd8e3d8 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -6,192 +6,260 @@
@import "./assets/scss/bootstrapOverrides";
@font-face {
- font-family: "Junicode";
- src: url("/assets/fonts/Junicode.woff") format("woff");
- font-weight: normal;
- font-style: normal;
+ font-family: "Junicode";
+ src: url("/assets/fonts/Junicode.woff") format("woff");
+ font-weight: normal;
+ font-style: normal;
}
@font-face {
- font-family: "Junicode";
- src: url("/assets/fonts/Junicode-Bold.woff") format("woff");
- font-weight: bold;
- font-style: normal;
+ font-family: "Junicode";
+ src: url("/assets/fonts/Junicode-Bold.woff") format("woff");
+ font-weight: bold;
+ font-style: normal;
}
@font-face {
- font-family: "Junicode";
- src: url("/assets/fonts/Junicode-Italic.woff") format("woff");
- font-weight: normal;
- font-style: italic;
+ font-family: "Junicode";
+ src: url("/assets/fonts/Junicode-Italic.woff") format("woff");
+ font-weight: normal;
+ font-style: italic;
}
@font-face {
- font-family: "Junicode";
- src: url("/assets/fonts/Junicode-BoldItalic.woff") format("woff");
- font-weight: bold;
- font-style: italic;
+ font-family: "Junicode";
+ src: url("/assets/fonts/Junicode-BoldItalic.woff") format("woff");
+ font-weight: bold;
+ font-style: italic;
}
html,
body {
- height: 100%;
+ height: 100%;
}
body {
- overflow: hidden;
+ overflow: hidden;
}
.edition-font {
- font-family: Junicode, Times, serif;
+ font-family: Junicode, Times, serif;
}
.ui-font {
- font-family: Arial, sans-serif; // TODO: decide which font to use for the UI
+ font-family: Arial, sans-serif; // TODO: decide which font to use for the UI
}
// ng-select overwrites
.ng-select {
- position: relative;
- display: inline-flex !important;
+ position: relative;
+ display: inline-flex !important;
+ vertical-align: middle;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ border-radius: 0.2rem;
+
+ .ng-select-container .ng-value-container .ng-placeholder {
+ top: 0 !important;
+ position: relative !important;
+ }
+
+ &.ng-select-focused:not(.ng-select-opened) > .ng-select-container {
+ border: none;
+ }
+
+ .ng-select-container {
+ height: 31px !important;
+ min-height: 0.6rem;
vertical-align: middle;
- font-size: 0.875rem;
- line-height: 1.5;
- border-radius: 0.2rem;
+ border: none;
+
+ @include themify($themes) {
+ background-color: themed("toolsBackground") !important;
+ color: themed("toolsColor");
+ }
+ }
- .ng-select-container .ng-value-container .ng-placeholder {
- top: 0 !important;
- position: relative !important;
+ .ng-arrow-wrapper {
+ padding-left: 0.35rem;
+ user-select: none;
+ height: 100%;
+ width: 1.5rem;
+ opacity: 0.5;
+
+ &:hover {
+ opacity: 1;
}
- &.ng-select-focused:not(.ng-select-opened) > .ng-select-container {
- border: none;
+ @include themify($themes) {
+ background-color: themed("toolsBackgroundDarker") !important;
}
- .ng-select-container {
- height: 31px !important;
- min-height: 31px;
- vertical-align: middle;
- border: none;
+ .ng-arrow {
+ border-width: 5px 5px 0;
+ vertical-align: text-bottom;
- @include themify($themes) {
- background-color: themed("toolsBackground") !important;
- color: themed("toolsColor");
- }
+ @include themify($themes) {
+ border-color: themed("baseColorDark") transparent transparent;
+ }
}
+ }
+ &.ng-select-opened > .ng-select-container {
.ng-arrow-wrapper {
- padding-left: 6px;
- user-select: none;
- height: 100%;
- width: 24px;
- opacity: 0.5;
-
- &:hover {
- opacity: 1;
- }
+ opacity: 1;
+ .ng-arrow {
@include themify($themes) {
- background-color: themed("toolsBackgroundDarker") !important;
- }
-
- .ng-arrow {
- border-width: 5px 5px 0;
- vertical-align: text-bottom;
-
- @include themify($themes) {
- border-color: themed("baseColorDark") transparent transparent;
- }
+ border-color: transparent transparent themed("baseColorDark");
}
+ }
}
+ }
- &.ng-select-opened > .ng-select-container {
- .ng-arrow-wrapper {
- opacity: 1;
-
- .ng-arrow {
- @include themify($themes) {
- border-color: transparent transparent themed("baseColorDark");
- }
- }
- }
+ .ng-dropdown-panel {
+ @include themify($themes) {
+ background-color: themed("toolsBackground") !important;
+ color: themed("toolsColor");
}
+ }
+ .ng-dropdown-panel-items {
+ @include set("box-shadow", "0 0.125rem 0.25rem rgba(0, 0, 0, 0.1)");
- .ng-dropdown-panel {
+ .ng-option {
+ @include themify($themes) {
+ background-color: themed("toolsBackground") !important;
+ color: themed("toolsColor");
+ }
+ &.ng-option-marked {
@include themify($themes) {
- background-color: themed("toolsBackground") !important;
- color: themed("toolsColor");
+ background-color: rgba(themed("baseColorDark"), 0.1) !important;
}
- }
- .ng-dropdown-panel-items {
- @include set("box-shadow", "0 0.125rem 0.25rem rgba(0, 0, 0, 0.1)");
-
- .ng-option {
- @include themify($themes) {
- background-color: themed("toolsBackground") !important;
- color: themed("toolsColor");
- }
- &.ng-option-marked {
- @include themify($themes) {
- background-color: rgba(themed("baseColorDark"), 0.1) !important;
- }
- }
- &.ng-option-selected {
- @include themify($themes) {
- background-color: themed("baseColorDark") !important;
- color: themed("baseColorLight") !important;
- }
- }
+ }
+ &.ng-option-selected {
+ @include themify($themes) {
+ background-color: themed("baseColorDark") !important;
+ color: themed("baseColorLight") !important;
}
+ }
}
+ }
- &.ng-select-opened {
- .ng-arrow-wrapper .ng-arrow {
- border-width: 0 4px 4px;
- }
+ &.ng-select-opened {
+ .ng-arrow-wrapper .ng-arrow {
+ border-width: 0 4px 4px;
}
+ }
}
.overflow-y-auto {
- overflow-y: auto;
+ overflow-y: auto;
}
.pointer {
- cursor: pointer;
+ cursor: pointer;
}
.abbr,
.expan {
- color: purple;
- text-decoration: underline;
+ color: purple;
+ text-decoration: underline;
}
.ex {
- font-style: italic;
+ font-style: italic;
}
.hidden {
- display: none;
+ display: none;
}
.btn-close {
- height: 20px !important;
- width: 20px !important;
- line-height: 0.8rem;
- padding: 0;
- font-size: 0.9rem;
+ height: 1.25rem !important;
+ width: 1.25rem !important;
+ line-height: 0.8rem;
+ padding: 0;
+ font-size: 0.9rem;
}
-.highlightverse{
- cursor: pointer;
- background-color: rgba(230,206,160,0.8); /*d5b17d e4c89b*/
- /*opacity: 0.8;
- filter:alpha(opacity=80); For IE8 and earlier */
- // border: 1px solid #b99562;
- color: #000000;
- padding: 3px 0px;
- &.selected{
- background-color: rgb(248, 230, 191);
- }
+evt-biblio-entry span:last-of-type {
+ display: none !important;
+}
+
+evt-biblio-list .msIdentifier {
+ margin-bottom: 2rem;
+ margin-left: 1em;
+ font-size: 90%;
+ line-height: 1.4rem;
+}
+
+evt-quote-entry evt-source-detail > div {
+ box-shadow: none !important;
+ margin: 0 !important;
+}
+
+evt-analogue-entry evt-analogue-detail > div {
+ box-shadow: none !important;
+ margin: 0 !important;
+}
+
+.popover {
+ min-width: 8rem;
+}
+
+.popover-body evt-quote-entry,
+.popover-body evt-analogue-entry {
+ pointer-events: none;
+}
+
+.biblio-list li {
+ margin-left: 1rem;
+}
+
+.source-detail-content p {
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+evt-analogue-detail .verse-num,
+evt-source-detail .verse-num {
+ display: none !important;
+}
+
+evt-analogue-entry evt-paragraph,
+evt-quote-entry evt-paragraph {
+ display: inline-block;
+}
+
+evt-analogue-entry evt-paragraph p,
+evt-quote-entry evt-paragraph p {
+ margin-bottom: 0;
+}
+
+evt-source-detail evt-original-encoding-viewer pre,
+evt-analogue-detail evt-original-encoding-viewer pre {
+ margin-top: -2% !important;
+ padding: 0 !important;
+ background-color: transparent !important;
+}
+
+evt-source-detail evt-original-encoding-viewer code,
+evt-analogue-detail evt-original-encoding-viewer code {
+ color: initial;
+ font-size: 85%;
}
+evt-source-detail evt-original-encoding-viewer code .token,
+evt-analogue-detail evt-original-encoding-viewer code .token {
+ color: #212529 !important;
+}
+
+.highlightverse{
+ cursor: pointer;
+ background-color: rgba(230,206,160,0.8);
+ color: #000000;
+ padding: 3px 0px;
+ &.selected{
+ background-color: rgb(248, 230, 191);
+ }
+}
\ No newline at end of file