diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 3061116d7..e4a46b42b 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -96,6 +96,16 @@ export interface UiConfig { initNavBarOpened: boolean; thumbnailsButton: boolean; viscollButton: boolean; + defaultBibliographicStyle: string; + allowedBibliographicStyles: { + [key: string]: { + id: string; + label: string; + enabled: boolean; + propsOrder: BibliographicProperties[]; + properties: BibliographicStyle; + } + }; mainFontFamily: string; mainFontSize: string; secondaryFontFamily: string; @@ -103,6 +113,27 @@ export interface UiConfig { theme: 'neutral' | 'modern' | 'classic'; syncZonesHighlightButton: boolean; } +export type CitingRanges = 'issue' | 'volume' | 'page'; +export type BibliographicProperties = 'author'| 'date'| 'title'| 'editor' | 'publication' | 'pubPlace' | 'publisher' | 'doi'; +export type BibliographicStyle = Partial<{ + propsDelimiter: string; + authorStyle: Partial<{ + forenameInitials: boolean; + delimiter: string; + lastDelimiter: string; + order: Array<'forename' | 'surname'>; + maxAuthors: string; + }>; + publicationStyle: Partial<{ + citingAcronym: 'all' | 'none' | CitingRanges[]; + includeEditor: boolean; + inBrackets: CitingRanges[]; + }>; + dateInsidePublication: boolean; + titleQuotes: boolean; + emphasized: BibliographicProperties[]; + inBrackets: BibliographicProperties[]; +}>; export interface EditionConfig { editionTitle: string; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3ca771054..4a549dba2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -33,6 +33,8 @@ import { ApparatusEntryComponent } from './components/apparatus-entry/apparatus- import { ApparatusEntryDetailComponent } from './components/apparatus-entry/apparatus-entry-detail/apparatus-entry-detail.component'; import { ApparatusEntryReadingsComponent } from './components/apparatus-entry/apparatus-entry-readings/apparatus-entry-readings.component'; import { BiblioEntryComponent } from './components/biblio/biblio.component'; +import { BibliographyInfoComponent } from './components/bibliography-info/bibliography-info.component'; +import { BibliographicStyleSelectorComponent } from './components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector'; import { BiblioListComponent } from './components/biblioList/biblio-list.component'; import { ChangeLayerSelectorComponent } from './components/change-layer-selector/change-layer-selector.component'; import { CharComponent } from './components/char/char.component'; @@ -124,6 +126,7 @@ import { SourceNoteComponent } from './components/sources/source-note/source-not import { SourcesComponent } from './components/sources/sources.component'; import { SourcesPanelComponent } from './panels/sources-panel/sources-panel.component'; import { StartsWithPipe } from './pipes/starts-with.pipe'; +import { StyledBiblioEntryComponent } from './components/bibliography-info/biblio-styled/biblio-styled.component'; import { SubstitutionComponent } from './components/substitution/substitution.component'; import { SuppliedComponent } from './components/supplied/supplied.component'; import { SurplusComponent } from './components/surplus/surplus.component'; @@ -215,6 +218,8 @@ const DynamicComponents = [ AnnotatorDirective, AppComponent, BiblioEntryComponent, + BibliographyInfoComponent, + BibliographicStyleSelectorComponent, BiblioListComponent, ChangeLayerSelectorComponent, CollationComponent, @@ -263,6 +268,7 @@ const DynamicComponents = [ SourcesComponent, SourcesPanelComponent, StartsWithPipe, + StyledBiblioEntryComponent, SubstitutionComponent, TextPanelComponent, TextSourcesComponent, diff --git a/src/app/components/biblio/biblio.component.html b/src/app/components/biblio/biblio.component.html index 3a28c67ee..9d909764d 100644 --- a/src/app/components/biblio/biblio.component.html +++ b/src/app/components/biblio/biblio.component.html @@ -3,7 +3,7 @@ {{element.value}}: - {{ data[element.value] }} , + {{ data[element.value] }} , 
diff --git a/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.html b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.html new file mode 100644 index 000000000..fd52d3f34 --- /dev/null +++ b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.html @@ -0,0 +1,63 @@ + + + + + + + {{ biblEntry.text }} + + + + + + + + + + + (){{styleProperties?.propsDelimiter || ","}} + + + + + +«{{ entries[0]?.titleDetails ? entries[0].titleDetails.title : entries[0].title }}» + + + + + + {{name === "forename" && styleProperties.authorStyle?.forenameInitials ? author.forenameInitials : author[name] }} + + {{ author.fullName }} + {{ index < count - 2 ? (styleProperties.authorStyle?.delimiter || ',') : (styleProperties.authorStyle?.lastDelimiter || " and") }}  + + + + + + {{ monogr.publication }} +  vol. {{monogr.volumeNumber}} + + , , + {{ styleProperties.publicationStyle?.inBrackets && styleProperties.publicationStyle?.inBrackets.includes('issue') ? '(' : ', ' }}no. {{styleProperties?.publicationStyle?.inBrackets && styleProperties.publicationStyle?.inBrackets.includes('issue')? monogr.issueNumber + ')' : monogr.issueNumber }} + () + : pp. {{monogr.pageNumber}} + , {{monogr[property]}}: +  {{ "reprintedIn" | translate }}  + + + + +{{ entries[0].date[0] || 's.d.' }} + +{{ entries[0].publisher }} + +{{ entries[0].pubPlace }} + + + {{ "editedBy" | translate}} {{editor}}, + + + +https://doi.org/{{entry.doi}} \ No newline at end of file diff --git a/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.scss b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.scss new file mode 100644 index 000000000..b6da677de --- /dev/null +++ b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.scss @@ -0,0 +1,7 @@ +.font-italic { + font-style: italic; +} + +.font-normal { + font-style: normal !important; +} \ No newline at end of file diff --git a/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.spec.ts b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.spec.ts new file mode 100644 index 000000000..85bcdfb3b --- /dev/null +++ b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BiblioEntryComponent } from './biblio.component'; + +describe('BiblioComponent', () => { + let component: BiblioEntryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BiblioEntryComponent ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BiblioEntryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.ts b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.ts new file mode 100644 index 000000000..99ce5dcd5 --- /dev/null +++ b/src/app/components/bibliography-info/biblio-styled/biblio-styled.component.ts @@ -0,0 +1,113 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AfterViewInit, ChangeDetectorRef, Component, Input, OnChanges, SimpleChanges, TemplateRef, ViewChild } from '@angular/core'; +import { AppConfig, BibliographicStyle, CitingRanges } from 'src/app/app.config'; +import { AuthorDetail, BibliographicEntry, BibliographicStructEntry } from 'src/app/models/evt-models'; + +@Component({ + selector: 'evt-styled-biblio-entry', + templateUrl: './biblio-styled.component.html', + styleUrls: ['./biblio-styled.component.scss'], +}) +export class StyledBiblioEntryComponent implements OnChanges, AfterViewInit { + + @ViewChild('title', { static: false }) title: TemplateRef; + @ViewChild('author', { static: false }) author: TemplateRef; + @ViewChild('publication', { static: false }) publication: TemplateRef; + @ViewChild('editor', { static: false }) editor: TemplateRef; + @ViewChild('date', { static: false }) date: TemplateRef; + @ViewChild('pubPlace', { static: false }) pubPlace: TemplateRef; + @ViewChild('publisher', { static: false }) publisher: TemplateRef; + @ViewChild('doi', { static: false }) doi: TemplateRef; + + + @Input() data: BibliographicEntry | BibliographicStructEntry; + @Input() style: string = AppConfig.evtSettings.ui.defaultBibliographicStyle; + + public biblEntry: any; + public showList: string[]; + public showAttrNames = AppConfig.evtSettings.edition.biblView.showAttrNames; + public showEmptyValues = AppConfig.evtSettings.edition.biblView.showEmptyValues; + public inline = AppConfig.evtSettings.edition.biblView.inline; + public isCommaSeparated = AppConfig.evtSettings.edition.biblView.commaSeparated; + public showMainElemTextContent = AppConfig.evtSettings.edition.biblView.showMainElemTextContent; + public styleProperties : BibliographicStyle; + + flattenBiblStruct(entry: BibliographicStructEntry): BibliographicEntry[] { + return entry.analytic.concat(entry.monogrs.concat(entry.series)); + } + + getContextForElement(element: string, structEntry: BibliographicStructEntry): BibliographicEntry[]{ + let context: BibliographicEntry[]; + switch(element){ + case 'title': + context = structEntry.analytic; + break; + case 'publication': + case 'date': + case 'publPlace': + case 'publisher': + case 'editor': + context = structEntry.monogrs; + break; + default: + context = this.flattenBiblStruct(structEntry); + break; + } + + return context; + } + + firstContainsProperty(entries: BibliographicEntry[], element:string): boolean{ + const elementInFirstEntry = entries[0]?.[element]; + + return elementInFirstEntry && elementInFirstEntry.length > 0; + } + + containsOnlyEmptyValues(arr: string[]): boolean{ + return arr.reduce((prev, x) => (x === '') && prev, true); + } + + requiresAcronym(elem: CitingRanges): boolean{ + const publicationStyle = this.styleProperties.publicationStyle || { citingAcronym: 'none' }; + if(!publicationStyle?.citingAcronym) { return false; } + + if(publicationStyle.citingAcronym === 'all'){ + return true; + }else if(publicationStyle.citingAcronym === 'none'){ + return false; + } + + return publicationStyle.citingAcronym.includes(elem); + } + + getPublisherDetailsOrder(): string[]{ + return this.showList.filter((x) => x === 'publisher' || x === 'pubPlace'); + } + + getAuthorsDetails(entries: BibliographicEntry[]): AuthorDetail[]{ + return entries.reduce((prev, e) => prev.concat(e.authorsDetails), []); + } + + isStructured(entry: BibliographicEntry): boolean{ + // searching for the most relevant signs of a structured entry. + return entry.originalEncoding.querySelectorAll('title, author, date').length > 0; + } + + ngOnChanges(changes: SimpleChanges): void { + if(this.data.type && this.data.type === BibliographicEntry){ + this.biblEntry = this.data as BibliographicEntry; + } + if(this.data.type && this.data.type === BibliographicStructEntry){ + this.biblEntry = this.data as BibliographicStructEntry; + } + if(changes.style){ + this.showList = AppConfig.evtSettings.ui.allowedBibliographicStyles[this.style].propsOrder; + this.styleProperties = AppConfig.evtSettings.ui.allowedBibliographicStyles[this.style].properties; + } + } + + constructor(private cd: ChangeDetectorRef){} + ngAfterViewInit(): void { + this.cd.detectChanges(); + } +} diff --git a/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.html b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.html new file mode 100644 index 000000000..d68940e51 --- /dev/null +++ b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.html @@ -0,0 +1,19 @@ +
+
+ +
+ + +
+
+
\ No newline at end of file diff --git a/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.scss b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.spec.ts b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.spec.ts new file mode 100644 index 000000000..820cc888b --- /dev/null +++ b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { BibliographicStyleSelectorComponent } from './bibliographic-style-selector'; + +describe('BibliographicStyleSelectorComponent', () => { + let component: BibliographicStyleSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ BibliographicStyleSelectorComponent ], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BibliographicStyleSelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.ts b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.ts new file mode 100644 index 000000000..722c0d6e6 --- /dev/null +++ b/src/app/components/bibliography-info/bibliographic-style-selector/bibliographic-style-selector.ts @@ -0,0 +1,27 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { AppConfig } from 'src/app/app.config'; + +@Component({ + selector: 'evt-bibliographic-style-selector', + templateUrl: './bibliographic-style-selector.component.html', + styleUrls: ['./bibliographic-style-selector.component.scss'], +}) +export class BibliographicStyleSelectorComponent implements OnInit { + public bibliographicStyles = (Object.values(AppConfig.evtSettings.ui.allowedBibliographicStyles) || []).filter((el) => el.enabled); + public selectedStyleID : string; + + @Output() selectionChange: EventEmitter = new EventEmitter(); + + ngOnInit(){ + this.selectedStyleID = AppConfig.evtSettings.ui.defaultBibliographicStyle; + this.selectionChange.emit(this.selectedStyleID); + } + + stopPropagation(event: MouseEvent) { + event.stopPropagation(); + } + + changeStyle(){ + this.selectionChange.emit(this.selectedStyleID); + } +} diff --git a/src/app/components/bibliography-info/bibliography-info.component.html b/src/app/components/bibliography-info/bibliography-info.component.html new file mode 100644 index 000000000..c57905a49 --- /dev/null +++ b/src/app/components/bibliography-info/bibliography-info.component.html @@ -0,0 +1,10 @@ +
+ +
+
+ + + + + +
\ No newline at end of file diff --git a/src/app/components/bibliography-info/bibliography-info.component.scss b/src/app/components/bibliography-info/bibliography-info.component.scss new file mode 100644 index 000000000..f038883d0 --- /dev/null +++ b/src/app/components/bibliography-info/bibliography-info.component.scss @@ -0,0 +1,12 @@ +.bibliography_header { + position: relative; + margin-top: 0; + height: 45px; + max-height: 45px; + text-align: center; +} + +.bibliography_header>div { + display: flex; + align-content: center; +} \ No newline at end of file diff --git a/src/app/components/bibliography-info/bibliography-info.component.spec.ts b/src/app/components/bibliography-info/bibliography-info.component.spec.ts new file mode 100644 index 000000000..7b346c49a --- /dev/null +++ b/src/app/components/bibliography-info/bibliography-info.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BibliographyInfoComponent } from './bibliography-info.component'; + +describe('BibliographyInfoComponent', () => { + let component: BibliographyInfoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BibliographyInfoComponent ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BibliographyInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/bibliography-info/bibliography-info.component.ts b/src/app/components/bibliography-info/bibliography-info.component.ts new file mode 100644 index 000000000..3b46b22b4 --- /dev/null +++ b/src/app/components/bibliography-info/bibliography-info.component.ts @@ -0,0 +1,23 @@ +import { Component, Input } from '@angular/core'; +import { BibliographicEntry, BibliographicStructEntry, BibliographyInfo } from 'src/app/models/evt-models'; +import { register } from 'src/app/services/component-register.service'; + +@Component({ + selector: 'evt-bibliography-info', + templateUrl: './bibliography-info.component.html', + styleUrls: ['./bibliography-info.component.scss'], +}) + +@register(BibliographyInfo) +export class BibliographyInfoComponent { + biblList : Array; + currentStyle : string; + + @Input() set data(bd : BibliographyInfo){ + this.biblList=bd.bibliographicEntries; + }; + + setCurrentStyle(s: string){ + this.currentStyle = s; + } +} diff --git a/src/app/components/project-info/project-info.component.html b/src/app/components/project-info/project-info.component.html index e16f40175..971bd619b 100644 --- a/src/app/components/project-info/project-info.component.html +++ b/src/app/components/project-info/project-info.component.html @@ -1,10 +1,11 @@
- - - - - + + + + + +
diff --git a/src/app/components/project-info/project-info.component.ts b/src/app/components/project-info/project-info.component.ts index ebfc2415d..61886fc47 100644 --- a/src/app/components/project-info/project-info.component.ts +++ b/src/app/components/project-info/project-info.component.ts @@ -14,6 +14,8 @@ export class ProjectInfoComponent { tap((info) => this.openSection('fileDesc', info.fileDesc)), ); + public bibliographicEntries$ = this.evtModelService.bibliographicEntries$; + public selectedSection: { key: string; content: GenericElement }; constructor( diff --git a/src/app/components/sources/source-note/source-note.component.ts b/src/app/components/sources/source-note/source-note.component.ts index dc561ff3b..7e6aa62ce 100644 --- a/src/app/components/sources/source-note/source-note.component.ts +++ b/src/app/components/sources/source-note/source-note.component.ts @@ -19,7 +19,7 @@ export class SourceNoteComponent implements OnInit { createNote(v, type): Note { const item = v[type]; - let content = item.extSources || []; + const content = item.extSources || []; if (type === 'analogue') { content.push( item.extLinkedElements ); content.push( item.sources ); diff --git a/src/app/models/evt-models.ts b/src/app/models/evt-models.ts index c85921705..54e3f77c5 100644 --- a/src/app/models/evt-models.ts +++ b/src/app/models/evt-models.ts @@ -207,15 +207,33 @@ export class BibliographicList extends GenericElement { sources: BibliographicEntry[]; } +export interface AuthorDetail { + fullName: string; + forename: string; + forenameInitials: string; + surname: string; + nameLink: string[]; +} export class BibliographicEntry extends GenericElement { id: string; author: string[]; + authorsDetails: AuthorDetail[]; editor: string[]; title: string[]; + titleDetails: { + title: string; + level: string; + }; + publication: string; + idno: string[]; + doi: string; date: string[]; publisher: string[]; pubPlace: string[]; citedRange: string[]; + pageNumber: string; + volumeNumber: string; + issueNumber: string; biblScope: string[]; text: string; quotedText: string; @@ -1336,6 +1354,10 @@ export class RevisionDesc extends GenericElement { status?: Status | string; } +export class BibliographyInfo { + bibliographicEntries: Array; +} + export class ProjectInfo { fileDesc: FileDesc; encodingDesc: EncodingDesc; diff --git a/src/app/services/evt-model.service.ts b/src/app/services/evt-model.service.ts index 3e2832c80..cc561048c 100644 --- a/src/app/services/evt-model.service.ts +++ b/src/app/services/evt-model.service.ts @@ -26,6 +26,7 @@ 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'; +import { BibliographicEntriesParserService } from './xml-parsers/bibliographic-entries-parser.service'; import { ModParserService } from './xml-parsers/mod-parser.service'; @Injectable({ @@ -357,6 +358,11 @@ export class EVTModelService { shareReplay(1), ); + public readonly bibliographicEntries$ = this.editionSource$.pipe( + map((source) => this.bibliographicEntriesParser.parseBibliographicEntries(source)), + shareReplay(1), + ) + constructor( private analogueParser: AnalogueEntriesParserService, private editionDataService: EditionDataService, @@ -370,6 +376,7 @@ export class EVTModelService { private linesVersesParser: LinesVersesParserService, private msDescParser: MsDescParserService, private sourceParser: SourceEntriesParserService, + private bibliographicEntriesParser: BibliographicEntriesParserService, private modParser: ModParserService, ) { } diff --git a/src/app/services/xml-parsers/analogue-parser.ts b/src/app/services/xml-parsers/analogue-parser.ts index 443353dbc..0244baa13 100644 --- a/src/app/services/xml-parsers/analogue-parser.ts +++ b/src/app/services/xml-parsers/analogue-parser.ts @@ -4,7 +4,7 @@ import { Analogue, AnalogueClass, BibliographicEntry, BibliographicList, Generic 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 { BibliographyParser } from './bibliography-parsers'; import { BasicParser } from './quotes-parser'; @xmlParser('evt-analogue-entry-parser', AnalogueParser) diff --git a/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts b/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts index 71ea2801c..f5bc7dea9 100644 --- a/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts +++ b/src/app/services/xml-parsers/bibliographic-entries-parser.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { ParserRegister } from '.'; -import { BibliographyClass, XMLElement } from '../../models/evt-models'; +import { BibliographyClass, BibliographyInfo, XMLElement } from '../../models/evt-models'; @Injectable({ providedIn: 'root', @@ -15,5 +15,14 @@ export class BibliographicEntriesParserService { .map((bib) => bibliographicParser.parse(bib)); } + parseBibliographicEntries(xml: XMLElement) { + const biblParser = ParserRegister.get(this.parserName); + + return { + type: BibliographyInfo, + bibliographicEntries: Array.from(xml.querySelectorAll('bibl')).map((s) => biblParser.parse(s)) + .concat(Array.from(xml.querySelectorAll('biblStruct')).map((s) => biblParser.parse(s))), + } + } } diff --git a/src/app/services/xml-parsers/bibliography-parsers.ts b/src/app/services/xml-parsers/bibliography-parsers.ts new file mode 100644 index 000000000..b7cb5bbeb --- /dev/null +++ b/src/app/services/xml-parsers/bibliography-parsers.ts @@ -0,0 +1,193 @@ +import { normalizeSpaces } from 'src/app/utils/xml-utils'; +import { parse, xmlParser } from '.'; +// eslint-disable-next-line max-len +import { AuthorDetail, 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: Element): string { + return s.textContent.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim(); + } + + protected getChildrenTextByName = function(xml : XMLElement, name : string): 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): string[] { + return Array.from(xml.querySelectorAll(name)).map((x) => + x.getAttribute(attribute) !== null ? + x.getAttribute(attribute)+' '+this.getTrimmedText(x) : + 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; + } + + protected getTitle(element: XMLElement): string { + const titles = this.getChildrenTextByName(element, 'title'); + let title = ''; + if(titles.length > 0){ + title = titles.shift(); + titles.forEach((el) => { title = title.replace(el, `"${el}"`); }); + } + + return title; + } + + protected getCitingText(element: XMLElement, includeUnit: boolean): string{ + const from = element.getAttribute('from'); + const to = element.getAttribute('to'); + const unit = element.getAttribute('unit'); + let returnString = ''; + if(unit && includeUnit){ + returnString = unit + ' '; + } + + if(element.textContent === '' && !element.hasChildNodes()){ + if(from){ + if(from === to){ + returnString += from; + }else{ + returnString += to ? `${from}-${to}` : `${from}ff`; + } + } + }else{ + returnString += this.getTrimmedText(element); + } + + return returnString; + } + + protected getCitingTags(element: XMLElement, name: string): string[] { + return Array.from(element.querySelectorAll(name)).map((el: XMLElement) => this.getCitingText(el, true)); + } + + + /** + * + * @param element the parent XML element where to search the bibliographic reference scope + * @param pattern a substring to search in \@unit, as it does not have a standard value. + * For example, to get a volume number the best approach would be to search for the "vol" substring. + * @returns the text representation of the bibliographic scope, along with the unit name. + */ + protected getBibliographicReferenceByUnitMatching(element: XMLElement, pattern: string): string{ + const biblScopeElement = element.querySelector('biblScope[unit*="' + pattern + '"]') + const citedRangeElement = element.querySelector('citedRange[unit*="' + pattern + '"'); + if(biblScopeElement || citedRangeElement){ + return biblScopeElement ? this.getCitingText(biblScopeElement, false) : this.getCitingText(citedRangeElement, false); + } + + return ''; + } + + protected getAuthorsDetails(element: XMLElement): AuthorDetail[]{ + const authors = Array.from(element.querySelectorAll('author')); + + return authors.map((el) => { + const forename = this.getChildrenTextByName(el as XMLElement, 'forename').reduce((prev, f) => prev + prev ? ' ' : '' + f, ''); + + return { + fullName: this.getTrimmedText(el), + forename, + forenameInitials: forename.replace(/\B(\w+)/g, '.'), + surname: this.getChildrenTextByName(el as XMLElement, 'surname').reduce((prev, s) => prev + prev ? ' ' : '' + s, ''), + nameLink: this.getChildrenTextByName(el as XMLElement, 'nameLink'), + } + }); + } + + protected getIdnoTextByType(element: XMLElement, type: string): string{ + const idno = element.querySelector('idno[type="' + type + '" i]'); + + return idno ? this.getTrimmedText(idno) : null; + } + + protected getDate(xml: XMLElement): string[]{ + return Array.from(xml.querySelectorAll('date')) + .map((x) => x.getAttribute('when') && !x.textContent ? x.getAttribute('when') : this.getTrimmedText(x)); + } + + // 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'), + titleDetails: { title: this.getTitle(xml), level: xml.querySelector('title')?.getAttribute('level') }, + publication: this.getTitle(xml), + idno: this.getChildrenTextByName(xml, 'idno'), + doi: this.getIdnoTextByType(xml, 'DOI'), + author: this.getChildrenTextByName(xml,'author'), + authorsDetails: this.getAuthorsDetails(xml), + editor: this.getChildrenTextByName(xml,'editor'), + date: this.getDate(xml), + publisher: this.getChildrenTextByName(xml,'publisher'), + pubPlace: this.getChildrenTextByName(xml,'pubPlace'), + citedRange: this.getCitingTags(xml, 'citedRange'), + biblScope: this.getCitingTags(xml, 'biblScope'), + pageNumber: this.getBibliographicReferenceByUnitMatching(xml, 'page'), + volumeNumber: this.getBibliographicReferenceByUnitMatching(xml, 'vol'), + issueNumber: this.getBibliographicReferenceByUnitMatching(xml, 'iss'), + 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/bilbliography-parsers.ts b/src/app/services/xml-parsers/bilbliography-parsers.ts deleted file mode 100644 index 8dd144837..000000000 --- a/src/app/services/xml-parsers/bilbliography-parsers.ts +++ /dev/null @@ -1,97 +0,0 @@ -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/quotes-parser.ts b/src/app/services/xml-parsers/quotes-parser.ts index 28c6dfe25..3a007ebae 100644 --- a/src/app/services/xml-parsers/quotes-parser.ts +++ b/src/app/services/xml-parsers/quotes-parser.ts @@ -6,7 +6,7 @@ import { Analogue, BibliographicEntry, BibliographicList, BibliographicStructEnt 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 { BibliographyParser } from './bibliography-parsers'; import { chainFirstChildTexts } from '../../utils/xml-utils'; import { GenericElemParser } from './basic-parsers'; diff --git a/src/app/ui-components/modal/modal.component.ts b/src/app/ui-components/modal/modal.component.ts index 544ee6c34..3fb00a1fb 100644 --- a/src/app/ui-components/modal/modal.component.ts +++ b/src/app/ui-components/modal/modal.component.ts @@ -49,7 +49,8 @@ export class ModalComponent implements OnInit { @HostListener('click', ['$event']) clickout(event) { const modal = this.modalDialog.nativeElement; - const internalClick: boolean = event.path.find((o) => o.className && o.className.indexOf && o.className.indexOf(modal.className) >= 0); + const path = event.composedPath ? event.composedPath() : event.path; + const internalClick: boolean = path.find((o) => o.className && o.className.indexOf && o.className.indexOf(modal.className) >= 0); if (this.closeOnShadow && !internalClick) { this.closeDialog(); } diff --git a/src/assets/config/ui_config.json b/src/assets/config/ui_config.json index adac1fcc2..2c60d1c9d 100644 --- a/src/assets/config/ui_config.json +++ b/src/assets/config/ui_config.json @@ -87,5 +87,81 @@ "secondaryfontFamily": "Arial, sans-serif", "secondaryfontSize": "1.1em", "theme": "modern", - "syncZonesHighlightButton": true + "syncZonesHighlightButton": true, + "defaultBibliographicStyle": "chicago", + "allowedBibliographicStyles": { + "chicago": { + "id": "chicago", + "label": "Chicago (Author-Date)", + "enabled": true, + "propsOrder": [ + "author", + "date", + "title", + "editor", + "publication", + "pubPlace", + "publisher", + "doi" + ], + "properties": { + "titleQuotes": true, + "emphasized": [ "publisher" ] + } + }, + "apa": { + "id": "apa", + "label": "APA", + "enabled": true, + "propsOrder": [ + "author", + "date", + "title", + "publication", + "publisher", + "doi" + ], + "properties": { + "authorStyle": { + "delimiter": ",", + "forenameInitials": true, + "maxAuthors": 3 + }, + "publicationStyle": { + "citingAcronym": "none", + "inBrackets": [ "issue" ] + }, + "propsDelimiter": ".", + "titleQuotes": false, + "emphasized": [ "publication" ], + "inBrackets": [ "date" ] + } + }, + "mla": { + "id": "mla", + "label": "MLA", + "enabled": true, + "propsOrder": [ + "author", + "title", + "publication", + "publisher", + "doi" + ], + "properties": { + "propsDelimiter": ".", + "authorStyle": { + "delimiter": "," + }, + "publicationStyle": { + "citingAcronym": "all", + "inBrackets": [ "issue" ], + "includeEditor": true + }, + "dateInsidePublication": true, + "titleQuotes": false, + "emphasized": [ "publication" ] + } + } + } } \ No newline at end of file diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 671130cdc..f32ac6c6d 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -173,12 +173,12 @@ "omit": "omit.", "wit": "Wit:", "xml": "XML", - "criticalApparatus":"Critical Apparatus", + "criticalApparatus": "Critical Apparatus", "sources": "Sources", "analogues": "Analogues", "source": "Source", "analogue": "Analogue", - "notes":"Notes", + "notes": "Notes", "refBibl": "Bibliographic references", "quotedSources": "Quoted sources", "quotedText": "Text", @@ -187,6 +187,15 @@ "corresps": "Correspondences", "addImage": "Add image", "removeImage": "Remove image", + "bibliography": "Bibliografia", + "style": "Style", + "sortBy": "Sort by", + "year": "Year", + "order": "Order", + "asc": "Ascendent", + "desc": "Descendent", + "reprintedIn": "Repr. in", + "editedBy": "Edited by" "corrSeq": "Correction sequence", "criticalNote": "Critical note", "showsDeletions": "Show deletions", diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 486e5ae00..a976dd4b1 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -186,6 +186,15 @@ "corresps": "Corrispondenze", "addImage": "Aggiungi immagine", "removeImage": "Rimuovi immagine", + "bibliography": "Bibliografia", + "style": "Stile", + "sortBy": "Ordina per", + "year": "Anno", + "order": "Ordine", + "asc": "Crescente", + "desc": "Decrescente", + "reprintedIn": "Rist. in", + "editedBy": "A cura di" "corrSeq": "Sequenza correzioni", "criticalNote": "Nota critica", "showsDeletions": "Mostra cancellazioni",