diff --git a/nietzsche-beta-app/src/app/app.module.ts b/nietzsche-beta-app/src/app/app.module.ts index d57f6dc..fd99ce8 100644 --- a/nietzsche-beta-app/src/app/app.module.ts +++ b/nietzsche-beta-app/src/app/app.module.ts @@ -1,105 +1,107 @@ import { AppComponent } from './app.component'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import {DragDropModule} from '@angular/cdk/drag-drop'; import { FormsModule, ReactiveFormsModule} from '@angular/forms'; import { NgModule } from '@angular/core'; import {MatCardModule} from '@angular/material/card'; import {MatButtonToggleModule, MatChipsModule} from '@angular/material'; import {MatFormFieldModule} from '@angular/material/form-field'; import { MatToolbarModule, MatButtonModule} from '@angular/material'; import {MatProgressBarModule} from '@angular/material/progress-bar'; import {MatTreeModule} from '@angular/material/tree'; import { MatExpansionModule } from '@angular/material/expansion'; import {MatIconModule} from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatMenuModule} from '@angular/material/menu'; import {MatRadioModule} from '@angular/material/radio'; import { MatSidenavModule } from '@angular/material'; import { MatSelectModule } from '@angular/material/select'; import {MatTabsModule} from '@angular/material/tabs'; import {MatTooltipModule} from '@angular/material'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { QueryService } from './services/query.service'; import {routing} from './app.routing'; import {HomeComponent} from './home.component'; import { ManuscriptViewComponentComponent } from './manuscript-view-component/manuscript-view-component.component'; import { ContentViewTabComponentComponent } from './content-view-tab-component/content-view-tab-component.component'; import { RhizomeViewComponentComponent } from './rhizome-view-component/rhizome-view-component.component'; import { MainMenuComponentComponent } from './main-menu-component/main-menu-component.component'; import { PageViewWrapperComponent } from './page-view-wrapper-component/page-view-wrapper.component'; import { NavigationListComponentComponent } from './navigation-list-component/navigation-list-component.component'; import {NavigationServiceService} from './services/navigation-service.service'; import { TlnEditionModule} from './tln-edition/tln-edition.module'; import { NavTree } from './navigation-list-component/navtree-directive.directive'; import { LazyImageLoadDirectiveDirective } from './content-view-tab-component/lazy-image-load-directive.directive'; import { ImpressumComponent } from './impressum.component'; import { ProjectComponent } from './project.component'; import { NavigationlistListComponentComponent } from './navigation-list-component/navigationlist-list-component/navigationlist-list-component.component'; import { CrossrefEditorComponentComponent } from './crossref-editor-component/crossref-editor-component.component'; import { PageCollectorComponentComponent } from './crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component'; import { LineSelectorComponentComponent } from './crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component'; import { TextVersionEditorComponent } from './crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component'; import { TextGenesisEditorComponent } from './crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component'; import { TextGeneseSelectorComponentComponent } from './crossref-editor-component/existing-text-genesis-workflow-component/text-genese-selector-component/text-genese-selector-component.component'; import { TextgeneseSelectorDialogueComponentComponent } from './crossref-editor-component/existing-text-genesis-workflow-component/text-genese-selector-component/textgenese-selector-dialogue-component/textgenese-selector-dialogue-component.component'; import { ExistingTextGenesisWorkflowComponentComponent } from './crossref-editor-component/existing-text-genesis-workflow-component/existing-text.genesis-workflow-component.component'; +import {RdfEditorModuleModule} from './rdf-editor-module/rdf-editor-module.module'; @NgModule({ declarations: [ AppComponent, HomeComponent, CrossrefEditorComponentComponent, PageCollectorComponentComponent, LineSelectorComponentComponent, TextVersionEditorComponent, TextGenesisEditorComponent, ManuscriptViewComponentComponent, ContentViewTabComponentComponent, RhizomeViewComponentComponent, MainMenuComponentComponent, PageViewWrapperComponent, NavigationListComponentComponent, NavTree, LazyImageLoadDirectiveDirective, ImpressumComponent, ProjectComponent, NavigationlistListComponentComponent, TextGeneseSelectorComponentComponent, TextgeneseSelectorDialogueComponentComponent, ExistingTextGenesisWorkflowComponentComponent ], imports: [ routing, BrowserModule, BrowserAnimationsModule, CommonModule, DragDropModule, HttpClientModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatChipsModule, MatExpansionModule, MatFormFieldModule, MatIconModule, MatListModule, MatMenuModule, MatProgressBarModule, MatRadioModule, MatSelectModule, // for themes selection MatSidenavModule, MatTabsModule, MatToolbarModule, MatTooltipModule, MatTreeModule, FormsModule, TlnEditionModule, - ReactiveFormsModule + ReactiveFormsModule, + RdfEditorModuleModule ], providers: [ NavigationServiceService, QueryService ], bootstrap: [AppComponent], entryComponents: [LineSelectorComponentComponent, TextgeneseSelectorDialogueComponentComponent] }) export class AppModule { } diff --git a/nietzsche-beta-app/src/app/constants.ts b/nietzsche-beta-app/src/app/constants.ts index a7159fb..6756f2a 100644 --- a/nietzsche-beta-app/src/app/constants.ts +++ b/nietzsche-beta-app/src/app/constants.ts @@ -1,396 +1,230 @@ import {NavTreeDef} from './models/models'; export const CONTENT_VIEW_ROUTE: string = 'contentView'; export const DOCUMENTATION_ROUTE: string = 'doku'; export const HOME_ROUTE: string = 'home'; export const IMPRESSUM_ROUTE: string = 'impressum'; export const PROJECT_ROUTE: string = 'project'; // endpoint && rdf related constants export const BASEURL: string = 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche'; export const EDITINGURL: string = 'http://localhost:3030/nietzsche-rw/query'; export const WRITEURL: string = 'http://localhost:3030/nietzsche-rw/update'; export const NAMESPACES: {} = { data: 'http://rdfh.ch/projects/0068#', tln: 'http://www.nie.org/ontology/nietzsche#' }; export const NAVTREE_DEFS: NavTreeDef[] = [ { id: 'manuscript', idx: 0, isActive: true, label: 'Manuskripte', itemQParam: 'manuscript', entries: [], apiDef: { type: 0, // rdf baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', dataArray: 'results.bindings', query: 'manuscripts.rq', mapping: { // maps the properties of the reponse to tha NavTabDef properties, which are displayed id: 'manuscript.value', // Short id, iri in most cases iri: 'manuscript.value', // iri type: 'type.value', label: 'title.value', description: 'gsaSignature.value', avatar: 'thumbImage.value' } } }, { id: 'page', idx: 1, isActive: false, label: 'Seiten', itemQParam: 'page', entries: [], apiDef: { type: 0, // rdf baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', dataArray: 'results.bindings', query: 'getPageData.rq', mapping: { id: 'page.value', // Short id, iri in most cases iri: 'page.value', // iri label: 'pageNumber.value', thumb: 'thumb.value', idx: 'pageNumber.value', svg: 'svgUrl.value', } } } ]; -export const RQ_AVAILABLE_PROPERTIES = ` -PREFIX rdfs: - -select DISTINCT ?prop ?range { - ?prop rdfs:domain ?anyClassToParametrize . - ?prop rdfs:range ?range . -}`; - -export const RQ_ASK_EXISTS_IN_STORE = ` -ASK WHERE { - ?subjectToParametrize ?p ?o -}`; - -export const RQ_ASK_IF_CLASS_INSTANCE = ` -PREFIX rdf: -PREFIX rdfs: -PREFIX owl: - -ASK WHERE { - ?iriToReplace rdf:type ?rdfType . - ?rdfType rdf:type/rdfs:subClassOf* owl:Class . - } -`; - -export const RQ_ASK_IF_PROPERTY = ` -PREFIX rdf: -PREFIX rdfs: -PREFIX owl: - -ASK WHERE { - ?thingToReplace rdf:type/rdfs:subClassOf* owl:ObjectProperty . -}`; - -export const RQ_RDF_Type = ` -PREFIX rdf: -PREFIX rdfs: - -SELECT ?rdf_type ?typeoftype WHERE { - ?thingToParametrize rdf:type ?rdf_type . - ?rdf_type rdf:type ?typeoftype . -}`; - -export const RQ_GET_PROPERTY_VALUE = ` -PREFIX rdfs: - -select ?value { - ?s ?p ?value -}`; - -export const RQ_GET_DOMAIN = ` -PREFIX rdfs: -PREFIX tln: - -select ?domain WHERE { - ?predicateToExchange rdfs:domain ?domain .} -`; - -export const RQ_GET_RANGE = ` -PREFIX rdfs: -PREFIX tln: - -select ?range WHERE { - ?predicateToExchange rdfs:range ?range . } -`; - - - -export const RQ_CONSTRUCT_LISTS = ` -prefix rdf: -PREFIX tln: - -CONSTRUCT { - ?thingToReplace ?propertyToReplace ?list . - ?listRest rdf:first ?head ; - rdf:rest ?tail . -} WHERE { - ?thing tln:hasGeneticOrder ?list . - - ?list rdf:rest* ?listRest . - ?listRest rdf:first ?head ; - rdf:rest ?tail . -} -`; - // Queries export const RQ_CROSSREF_TREE_MANUSCRIPTS: string = ` PREFIX data: PREFIX tln: PREFIX rdf: SELECT ?manuscript ?title ?thumbImage ?gsaSignature WHERE { ?manuscript a tln:ManuscriptUnity; tln:hasTitle ?title; tln:hasManuscriptType "Mappe"; tln:hasPages/rdf:first/tln:hasFaksimileImage/tln:hasThumburl ?thumbImage ; tln:hasGsaSignature ?gsaSignature . }`; export const RQ_CROSSREF_TREE_PAGES: string = ` PREFIX tln: PREFIX rdf: PREFIX xsd: SELECT DISTINCT ?page ?pageNumber ?type WHERE { ?s (tln:hasPages/(rdf:rest*)/rdf:first) ?page. ?page tln:hasNumber ?pageNumber. ?page a tln:Page. BIND (tln:page AS ?type) BIND (xsd:integer(REPLACE(?pageNumber, "\\\\D+", "")) AS ?sorting) } ORDER BY (?sorting) `; export const RQ_CROSSREF_TREE_LINES: string = ` PREFIX tln: PREFIX rdf: PREFIX xsd: SELECT DISTINCT ?line ?lNumber ?type WHERE { ?s (tln:hasLines/(rdf:rest*)/rdf:first) ?line. ?line a tln:Line. BIND (tln:line AS ?type) OPTIONAL { ?line tln:lineHasNumber ?lNumber. } } ORDER BY (?lNumber) `; export const RQ_CROSSREF_TREE_WORDS: string = ` PREFIX tln: PREFIX rdf: PREFIX xsd: PREFIX homotypic: SELECT DISTINCT ?word ?line ?text ?type WHERE { ?word tln:wordBelongsToLine ?line. ?word a tln:SimpleWord. BIND(tln:SimpleWord AS ?type) OPTIONAL { ?word homotypic:hasText ?text. } } ORDER BY (?word) `; -export const CROSSREF_TREE_DEFS: NavTreeDef[] = [ - { id: 'manuscript', - idx: 0, - isActive: true, - label: 'Manuskript ', - itemQParam: 'manuscript', - entries: [], - apiDef: { - type: 0, // rdf - baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', - dataArray: 'results.bindings', - query: RQ_CROSSREF_TREE_MANUSCRIPTS, - mapping: { // maps the properties of the reponse to tha NavTabDef properties, which are displayed - id: 'manuscript.value', // Short id, iri in most cases - iri: 'manuscript.value', // iri - type: 'type.value', - label: 'title.value', - description: 'gsaSignature.value', - avatar: 'thumbImage.value' - } - } - }, { - id: 'page', - idx: 1, - isActive: false, - label: 'S. ', - itemQParam: 'page', - entries: [], - apiDef: { - type: 0, // rdf - baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', - dataArray: 'results.bindings', - query: RQ_CROSSREF_TREE_PAGES, - mapping: { - id: 'page.value', // Short id, iri in most cases - iri: 'page.value', // iri - label: 'pageNumber.value', - type: 'type.value' - } - } - }, { - id: 'line', - idx: 2, - isActive: false, - label: 'Zeile ', - itemQParam: '', - entries: [], - apiDef: { - type: 0, // rdf - baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', - dataArray: 'results.bindings', - query: RQ_CROSSREF_TREE_LINES, - mapping: { - id: 'line.value', // Short id, iri in most cases - iri: 'line.value', // iri - label: 'lNumber.value', - type: 'type.value' - } - } - }, { - id: 'word', - idx: 3, - isActive: false, - label: '', - itemQParam: '', - entries: [], - apiDef: { - type: 0, // rdf - baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', - dataArray: 'results.bindings', - query: RQ_CROSSREF_TREE_WORDS, - mapping: { - id: 'word.value', // Short id, iri in most cases - iri: 'word.value', // iri - label: 'text.value', - type: 'type.value' - }, - paramTriple: 2 - } - } -]; - // GENERIC TREES // Queries export const RQ_GENERIC_TREE_ROOT: string = ` PREFIX data: PREFIX tln: PREFIX rdf: SELECT DISTINCT ?manuscript ?title ?type ?gsaSignature WHERE { ?manuscript a tln:ManuscriptUnity; tln:hasTitle ?title; tln:hasManuscriptType "Mappe"; tln:hasGsaSignature ?gsaSignature . OPTIONAL { tln:ManuscriptUnity a ?type } }`; export const RQ_GENERIC_RDF_PROPERTY_TREE: string = ` PREFIX rdfs: SELECT DISTINCT ?p ?type ?label ?comment WHERE { ?s ?p ?o. OPTIONAL { ?p rdfs:label ?label. ?p rdfs:comment ?comment. ?p a ?sth. } } ORDER BY (?label) LIMIT 10 `; export const RQ_GENERIC_OBJECT_TREE: string = ` PREFIX rdfs: SELECT DISTINCT ?o ?type ?label ?comment WHERE { ?s ?p ?o. OPTIONAL { ?o rdfs:label ?label. ?o rdfs:comment ?comment. ?o a ?sth. } } ORDER BY (?label) LIMIT 10 `; export const GENERIC_ROOT_TREE_DEF: NavTreeDef[] = [ { id: 'root', idx: 0, isActive: true, label: 'Manuskript ', itemQParam: 'manuscript', entries: [], apiDef: { type: 0, // rdf baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', dataArray: 'results.bindings', query: RQ_GENERIC_TREE_ROOT, mapping: { // maps the properties of the reponse to tha NavTabDef properties, which are displayed id: 'manuscript.value', // Short id, iri in most cases iri: 'manuscript.value', // iri type: 'type.value', label: 'title.value', description: 'gsaSignature.value' } } }, { id: 'property', idx: 1, isActive: true, label: 'Property ', itemQParam: '', entries: [], apiDef: { type: 0, // rdf baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', dataArray: 'results.bindings', query: RQ_GENERIC_RDF_PROPERTY_TREE, mapping: { // maps the properties of the reponse to tha NavTabDef properties, which are displayed id: 'p.value', // Short id, iri in most cases iri: 'p.value', // iri type: 'type.value', label: 'label.value', description: 'comment.value' } } }, { id: 'object', idx: 1, isActive: true, label: 'Object ', itemQParam: '', entries: [], apiDef: { type: 0, // rdf baseUrl: 'https://nietzsche.fuseki.services.dasch.swiss/nietzsche',//'http://fuseki.nie-ine.ch/nietzsche-rw/query', dataArray: 'results.bindings', query: RQ_GENERIC_OBJECT_TREE, mapping: { // maps the properties of the reponse to tha NavTabDef properties, which are displayed id: 'o.value', // Short id, iri in most cases iri: 'o.value', // iri type: 'type.value', label: 'label.value', description: 'comment.value' } } } ]; diff --git a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts index 5f81b06..7e0b784 100644 --- a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts +++ b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-routes.ts @@ -1,16 +1,21 @@ import { Routes } from '@angular/router'; import {ManuscriptViewComponentComponent} from "../manuscript-view-component/manuscript-view-component.component"; import {RhizomeViewComponentComponent} from "../rhizome-view-component/rhizome-view-component.component"; import {PageViewWrapperComponent} from "../page-view-wrapper-component/page-view-wrapper.component"; -import { TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_ROUTE, TLN_CROSSREF_EDITOR_ROUTE } from '../tln-edition/constants'; +import { + TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_ROUTE, TLN_CROSSREF_EDITOR_ROUTE, + TLN_RESSOURCE_EDITOR_ROUTE +} from '../tln-edition/constants'; import { TlnFulltextComponent } from '../tln-edition/tln-fulltext/tln-fulltext.component'; import {CrossrefEditorComponentComponent} from "../crossref-editor-component/crossref-editor-component.component"; +import {RdfRsourceEditorComponentComponent} from '../rdf-editor-module/rdf-resource-editor-component/rdf-rsource-editor-component.component'; export const CONTENT_ROUTES: Routes = [ { path: TLN_MANUSCRIPT_ROUTE, component: ManuscriptViewComponentComponent }, { path: TLN_VIEWER_ROUTE, component: PageViewWrapperComponent }, { path: TLN_CROSSREF_ROUTE, component: RhizomeViewComponentComponent }, { path: TLN_CROSSREF_EDITOR_ROUTE, component: CrossrefEditorComponentComponent}, { path: TLN_SEARCH_ROUTE, component: TlnFulltextComponent}, + { path: TLN_RESSOURCE_EDITOR_ROUTE, component: RdfRsourceEditorComponentComponent}, { path: '', redirectTo: TLN_MANUSCRIPT_ROUTE, pathMatch: 'prefix' } ]; diff --git a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts index 42327a3..56b5a7a 100644 --- a/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts +++ b/nietzsche-beta-app/src/app/content-view-tab-component/content-view-tab-component.component.ts @@ -1,137 +1,143 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, NavigationEnd, Params, Router} from '@angular/router'; import {Subscription} from "rxjs/index"; import {NavigationServiceService} from "../services/navigation-service.service"; import { - TLN_CROSSREF_EDITOR_ROUTE, TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_SEARCH_ROUTE, + TLN_CROSSREF_EDITOR_ROUTE, TLN_CROSSREF_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_RESSOURCE_EDITOR_ROUTE, TLN_SEARCH_ROUTE, TLN_VIEWER_ROUTE, } from '../tln-edition/constants'; @Component({ selector: 'app-content-view-tab-component', templateUrl: './content-view-tab-component.component.html', styleUrls: ['./content-view-tab-component.component.scss'], providers: [ NavigationServiceService ] }) export class ContentViewTabComponentComponent implements OnInit { // navigation tabs for the content view (manuscript view, page view, rhizome view) navTabLinks: any[]; // navbar on th left for navigating navBarOpenState = false; fullscreen = false; navBarOpenMode: string; queryParams: Params = {}; queryParamSubscription: Subscription; routeSubscription: Subscription; constructor(private router: Router, private activatedRoute: ActivatedRoute, private naviService: NavigationServiceService) { // The links/tabs for routing the correct view-component this.navTabLinks = [ { label: 'Manuskriptansicht', link: TLN_MANUSCRIPT_ROUTE, index: 0, isActive: true, context: 'manuscript', // in which context the viewers are opened disabled: false }, { label: 'Seitenansicht', link: TLN_VIEWER_ROUTE, index: 1, isActive: false, context: 'page', // in which context the viewers are opened disabled: false }, { label: 'Querverweise', link: TLN_CROSSREF_ROUTE, index: 2, isActive: false, disabled: false }, { label: 'Suche', link: TLN_SEARCH_ROUTE, index: 3, isActive: false, disabled: false }, { label: 'Querverweis-Editor', link: TLN_CROSSREF_EDITOR_ROUTE, index: 4, isActive: false, disabled: false + },{ + label: 'Ressourcen-Editor', + link: TLN_RESSOURCE_EDITOR_ROUTE, + index: 5, + isActive: false, + disabled: false }, ]; // subscribe to route event and for marking the active navTab as active this.routeSubscription = this.router.events.subscribe((event) => { if ( event instanceof NavigationEnd) { // if the navigated link is not active, we set it acive if ( !this.navTabLinks.find(nl => nl.link === this.activatedRoute.snapshot.children.pop().routeConfig.path).isActive ) { this.setActiveLink(this.activatedRoute.snapshot.children.pop().routeConfig.path); } } }); this.queryParamSubscription = this.activatedRoute.queryParams.subscribe( (queryParams: Params ) => { this.queryParams = queryParams; if (queryParams.navBarOpenState) { this.navBarOpenState = JSON.parse(queryParams.navBarOpenState.toLowerCase()); } if (queryParams.fullscreen) { this.fullscreen = JSON.parse(queryParams.fullscreen.toLowerCase()); } }); } ngOnInit() { this.navBarOpenMode = 'side'; // side || over || push this.setParamsOnInit(); // If url pasted or page refreshed --> resetting this.queryparams to the query params of the url; // needed for active routing in the nav tabs & for general use in the template } // Sets the isActive prop of a navTabLink to true and all others to false . setActiveLink(link: string) { this.navTabLinks.forEach((navTabLink, index ) => { if (navTabLink.link === link) { this.navTabLinks[index].isActive = true; if (navTabLink.context) { window.setTimeout(() => this.naviService.updateRoute({contextView : navTabLink.context}), 300); } } else { this.navTabLinks[index].isActive = false; } }); } /** * setParamsOnInit checks if the page is loaded with query params. If a certain query param is missing, it will be set to a default value * */ setParamsOnInit() { const qParams: Params = {}; // Set the NavBarOpenstate to true if it is not defined explicitely as false in the url query param onInit. if (this.activatedRoute.snapshot.queryParamMap.get('navBarOpenState') !== 'false') { qParams.navBarOpenState = 'true'; } // viewMode if (!this.activatedRoute.snapshot.queryParamMap.get('viewMode')) { qParams.viewMode = 'Transkription/Faksimile'; } // contextView if (!this.activatedRoute.snapshot.queryParamMap.get('contextView')) { qParams.contextView = 'manuscript'; } // navTabIdx: if none default is null if (!this.activatedRoute.snapshot.queryParamMap.get('navTabIdx')) { if (this.activatedRoute.snapshot.queryParamMap.get('contextView') === 'manuscript' || qParams.contextView === 'manuscript') { qParams.navTabIdx = '0'; } else { qParams.navTabIdx = '1'; } } this.naviService.updateRoute(qParams); } } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-component.component.ts b/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-component.component.ts index cf6727f..8e3a475 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-component.component.ts +++ b/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-component.component.ts @@ -1,41 +1,39 @@ -import {Component, OnChanges} from '@angular/core'; -import {CrossrefEditorDataServiceService, TextGenesis, TextVersion} from './crossref-editor-data-service.service'; +import {Component, OnChanges, OnInit} from '@angular/core'; +import {CrossrefEditorDataServiceService, TextGenesis, GuiTextVersion} from './crossref-editor-data-service.service'; import {ActivatedRoute} from '@angular/router'; import {TlnQueryService} from '../tln-edition/tln-query.service'; import {QueryService} from '../services/query.service'; +import {ChangeMgmntDef} from '../rdf-editor-module/statement-handler'; @Component({ selector: 'app-crossref-editor-component', templateUrl: './crossref-editor-component.component.html', styleUrls: ['./crossref-editor-component.component.scss'] }) -export class CrossrefEditorComponentComponent implements OnChanges { +export class CrossrefEditorComponentComponent implements OnInit { textGenesis: TextGenesis; editExistingTextGenesis = false; // whether an existing Textgenesis should be edited addNewTextGenesis = false; // whether an existing Textgenesis should be edited constructor(private dataService: CrossrefEditorDataServiceService, - private activatedRoute: ActivatedRoute, - private queryService: QueryService) { + private activatedRoute: ActivatedRoute) { } - ngOnChanges() { - } + ngOnInit() {} initNewTextgenesis() { this.addNewTextGenesis = true; - this.textGenesis = new TextGenesis( this.queryService, 'TextGenesis', '', true, this.activatedRoute.snapshot.queryParamMap.get('manuscript')); + this.textGenesis = new TextGenesis('TextGenesis', '', this.activatedRoute.snapshot.queryParamMap.get('manuscript'), true); this.textGenesis.checkOut(true); // also adding new text version - let version = new TextVersion(this.queryService, '', this.textGenesis.iri, false, false, this.textGenesis.iri ); - this.textGenesis.addTextVersion(version, 0); - // emit to service + let version = GuiTextVersion.buildNew(this.textGenesis.iri()); + this.textGenesis.addTextVersion(version, this.textGenesis.textVersions().length); this.dataService.updateAll(this.textGenesis); } editExisting() { this.editExistingTextGenesis = true; } } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-data-service.service.ts b/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-data-service.service.ts index 96d1011..0d35933 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-data-service.service.ts +++ b/nietzsche-beta-app/src/app/crossref-editor-component/crossref-editor-data-service.service.ts @@ -1,1150 +1,901 @@ -import {EventEmitter, Injectable, Predicate} from '@angular/core'; +import {EventEmitter, Injectable} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; -import { - BASEURL, EDITINGURL, NAMESPACES, RQ_ASK_EXISTS_IN_STORE, RQ_ASK_IF_CLASS_INSTANCE, RQ_ASK_IF_PROPERTY, RQ_AVAILABLE_PROPERTIES, - RQ_CONSTRUCT_LISTS, - RQ_CROSSREF_TREE_LINES, RQ_GET_DOMAIN, - RQ_GET_PROPERTY_VALUE, RQ_GET_RANGE, RQ_RDF_Type, - WRITEURL -} from '../constants'; +import { BASEURL, RQ_CROSSREF_TREE_LINES, WRITEURL } from '../constants'; import {QueryService} from '../services/query.service'; -import * as N3 from "node_modules/n3/src"; +import * as N3 from 'node_modules/n3/src'; import {moveItemInArray} from '@angular/cdk/drag-drop'; -import {NavigationEntity, TlnEntity} from '../models/models'; +import {NavigationEntity} from '../models/models'; import {TlnLine} from '../tln-edition/datatypes/line'; import {PageViewService} from '../page-view/page-view.service'; import {TlnQueryServiceInterface} from '../tln-edition/models'; -import {NamedNode} from 'rdf-js'; -import {Subscription} from 'rxjs'; -import {__await} from 'tslib'; +import {SafeUrl} from '@angular/platform-browser'; +import {ChangeMgmntDef, TlnPredicate} from '../rdf-editor-module/statement-handler'; +import {RdfData} from '../rdf-editor-module/rdf-editor-DataSets'; +import {TlnResource, TlnTransaction} from '../rdf-editor-module/editor-resources'; @Injectable({ providedIn: 'root' }) export class CrossrefEditorDataServiceService { textGenesis: TextGenesis; // the one textGenesis used by all components textGenesisEmitter: EventEmitter; + transaction = new TlnTransaction(); + def: ChangeMgmntDef; - textUnitDataBase: Map; - textUnitDataBaseEmitter: EventEmitter>; queryService: TlnQueryServiceInterface; queryServiceEmitter: EventEmitter; constructor(private activatedRoute: ActivatedRoute, private qService: QueryService, private pageViewService: PageViewService) { - this.textUnitDataBase = new Map(); - this.textUnitDataBaseEmitter = new EventEmitter>(); this.queryServiceEmitter = new EventEmitter(); - this.textGenesisEmitter = new EventEmitter(); + // creating a def: keep the patient statements as they got deleted by their agents anyway + this.def = new ChangeMgmntDef( + true, + true, + false, + false, + true, + [], + [], + [], + [], + false, + {keepAgentLists: false, keepAgentStatements: false, keepPatientLists: true, keepPatientStatements: true} + ); // subcribing at klicked lines to set them as selection this.pageViewService.onClickedLine.subscribe((clickedLine: TlnLine) => { - if (clickedLine.page === this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().belongsToPage) { + if (clickedLine.page === this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().pageData.pageIri) { this.setLine(clickedLine); this.updateAll(this.textGenesis); } }) } readInTextGenesis(textGenesisIri: string) { textGenesisIri = 'http://rdfh.ch/projects/0068#_Mp_XIV_TextGenesis0' ; // Todo: make generic - const textGenesis = new TextGenesis(this.qService, 'Textgenese', textGenesisIri, true ); - this.updateAll(textGenesis); - - + TextGenesis.buildAsyncFromStore(this.qService, textGenesisIri).then(textGenesis => + this.updateAll(textGenesis)); } + resetFromStore() { + TextGenesis.buildAsyncFromStore(this.qService, this.textGenesis.iri()).then(textGenesis => + this.updateAll(textGenesis)); + } // sets the lines: if start and end are existing it resets the startline else sets the endline setLine(line: TlnLine) { // get the active page ... - if (!this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().hasStartLine() || ( - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().hasStartLine() && - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().hasEndLine() ) + if (!this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().textUnit.hasStartLine() || ( + this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().textUnit.hasStartLine() && + this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().textUnit.hasEndLine() ) ) { - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().setStartLine(line.id, line.number); + this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().textUnit.setStartLine(line.id, line.number); } else { // endLine must be set - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().setEndLine(line.id, line.number); + this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().textUnit.setEndLine(line.id, line.number); } } updateAll(textGenesis:TextGenesis) { this.textGenesis = textGenesis; this.textGenesisEmitter.emit(textGenesis); } // sets the textVersion to external/internal and checksout/unchecks the active one. setExternityOfTextVersion(setToExternal: boolean) { - this.textGenesis.checkedOutTextVersion().isExternalResource = setToExternal; - // uncheckout - !setToExternal ? this.textGenesis.checkedOutTextVersion().externalTextVersion.checkOut(false) : - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkOut(false); - // checkout - setToExternal ? this.textGenesis.checkedOutTextVersion().externalTextVersion.checkOut(true) : - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkOut(true); + this.textGenesis.checkedOutTextVersion().changeExternity(setToExternal); this.updateAll(this.textGenesis); } - addPage(navItem: NavigationEntity, index: number){ - const textUnit = new TextUnit( - this.qService, - 'textResource', - '', - false, - 'http://www.nie.org/ontology/nietzsche#PartOfPageTextUnit', - navItem.tlnEntity.iri, - true, - navItem.tlnEntity.description, - navItem.tlnEntity.label, - navItem); - this.textGenesis.checkedOutTextVersion().internalTextVersion.setTextUnit(textUnit, index); + public deleteGuiTextVersion(iri) { + this.textGenesis.deleteTextVersion(iri); this.updateAll(this.textGenesis); } + public unDeleteGuiTextVersion(iri) { + this.textGenesis.unDeleteTextVersion(iri); + this.updateAll(this.textGenesis); + } + + + // builds a GuiTextUnit with the added and queried pages data and adds it to the textVersion + onAddingPage(navItem: NavigationEntity, index: number){ + // Todo: Change to nonStore constructor!!! instore = false + GuiTextUnit.buildAsyncFromStore(this.qService, navItem.tlnEntity.iri, 'Texteinheit', this.textGenesis.checkedOutTextVersion().iri()) + .then(guiTextUnit => { + this.textGenesis.checkedOutTextVersion().internalTextVersion.addNewGuiTextUnit(guiTextUnit, index); + this.updateAll(this.textGenesis); + }); + } movePageInArray(previousIndex: number, index: number) { - this.textGenesis.checkedOutTextVersion().internalTextVersion.moveTextUnitInArray(previousIndex, index) + this.textGenesis.checkedOutTextVersion().internalTextVersion.moveTextUnitInArray(previousIndex, index); this.updateAll(this.textGenesis); } - removeTextUnit(textUnitIri: string) { - this.textGenesis.checkedOutTextVersion().internalTextVersion.deleteTextUnit(textUnitIri); + onRemovePage(iri: string) { + this.textGenesis.checkedOutTextVersion().internalTextVersion.deleteGuiTextUnit(iri); this.updateAll(this.textGenesis); } - setWholePage(textUnit: TextUnit, truthVal: boolean) { - this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().setWholePage(truthVal); - const idx = this.textGenesis.checkedOutTextVersion().internalTextVersion.getTextUnitIdxByIri(textUnit.iri); - // Have to change statements because of ontology: - if (truthVal) { // If wholePage = true, there is no textUnit to store - it is the page itself - this.textGenesis.checkedOutTextVersion().internalTextVersion._rdfDatasetToEdit.setStatement('identifiesAsVersion', textUnit.belongsToPage, idx); - } else { this.textGenesis.checkedOutTextVersion().internalTextVersion._rdfDatasetToEdit.setStatement('identifiesAsVersion', textUnit.iri, idx); } + undeleteGuiTextUnit(iri: string) { + this.textGenesis.checkedOutTextVersion().internalTextVersion.unDeleteGuiTextUnit(iri); + this.updateAll(this.textGenesis); + } + // resets wholepage: boolean to the desired truthVal + changeWholePage(textUnit: TextUnit, truthVal: boolean) { + if (this.textGenesis.checkedOutTextVersion().internalTextVersion.checkedOutTextUnit().wholePage === truthVal ) { return } // guard + // getting the idx of the guitextUnit (may be a page or a textUnit) + this.textGenesis.checkedOutTextVersion().changeWholePage(textUnit, truthVal); this.updateAll(this.textGenesis) } - async writeOut(store: boolean) { - let textGeneseWriter = new N3.Writer(); - await this.textGenesis.getQuads().then( quads => { - console.log('textGenesis', this.textGenesis); - console.log('allQuads', quads); - textGeneseWriter.addQuads(quads); - }).then(cool => { - textGeneseWriter.end((error, result) => { - this.textGenesis.ttl_export = result; - if (store) { // importing to store - this.qService.updateData(WRITEURL, result); - } + // collects all tlnResources into one array update/delete/create + collectResources(): TlnResource[] { + let resources: TlnResource[] = []; + resources.push(this.textGenesis.resource); + this.textGenesis.childResources.forEach(child => { + resources.push(child.externalTextVersion.resource); // add ext resource + resources.push(child.internalTextVersion.resource); // int resource + child.internalTextVersion.childResources.forEach(iChild => { + resources.push(iChild.textUnit.resource); + console.log('iChild', iChild) }); }); + return resources; + } + + writeOut(store: boolean) { + let resources = this.collectResources(); + console.log('resources', resources); + console.log('del Resources ', resources.filter(res => res.isDeleted() && !res.ignore()).map(res => res.iri)); + console.log('stored Resources ', resources.filter(res => res.isInStore()).map( res => res.iri)); + console.log('irgnored Resources ', resources.filter(res => res.ignore()).map( res => res.iri)); + this.transaction = new TlnTransaction(); + this.transaction.commit(resources, '', '', this.def ).then(committed => { + this.transaction.committed = committed; + console.log('committed', committed); + if (committed === false) { console.log('Nothing to commit. No changes made.')} else { + console.log('committed', committed); + console.log('TRANSACTION', this.transaction) + this.transaction.preview(); + } + }); } - readInTtl (ttlData: string, prefixes?) { + readInTtl (ttlData: string) { const parser = new N3.Parser(), store = new N3.Store(); parser.parse(ttlData, function (error, triple, prefixes) { if (triple) store.addTriple(triple); else // is complete onComplete(); return store; }); function onComplete() { const republic = store.find('http://example.org/library/the-republic', null, null)[0]; console.log(store); } parser.parse(this.textGenesis.ttl_export, (error, quad, prefixes) => { if (quad) { store.addQuad(quad); } else - console.log("# That's all, folks!", prefixes); + console.log('# That\'s all, folks!', prefixes); }); console.log('my store ', store); } } -export class CrossRefEditorElement { - private checkedOut: boolean; // wether the thing is checked out for editing atm or not. - - constructor(){ +// main super class with basic functionality inherited by all gui classes +export class CrossRefEditorElement { + protected _iri: string; + private _checkedOut: boolean; // wether the thing is checked out for editing atm or not. + protected _paerentIri: string; + protected label: string; + protected parentLabel: string; + resource: TlnResource; // the equivalent rdf resource from store and for editing + childsPredicate: TlnPredicate; + childResources: any[]; // Other CrossrefEditorElements or a subclass/extension + public deleted = false; + public displayedPredicates: string[]; + public mutablePredicates: string[]; + + // Todo: refactor: set label inside subclasses - not via passing a parameter? + constructor(iri?: string, label?: string, parentIri?: string, checkOut = false, parentLabel?: string){ + if (iri !== '') { + this._iri = iri; + } else {// any random iri - does not matter + this._iri = RdfData.createIri(label, parentIri); } + this._paerentIri = parentIri; + this._checkedOut = checkOut; + this.parentLabel = parentLabel; + this.resource = new TlnResource(this._iri, label); } checkOut(val: boolean) { - this.checkedOut = val; + this._checkedOut = val; } isCheckedOut() { - return this.checkedOut; - } - - // builds an unique iri with the given iri, its parent and a generated hash - static createIri(label: string, parentIri?: string) { - const hash = `${Date.now().valueOf().toString(36)}-${(Math.random()).toString(36).replace('.', '')}`; // unique hash: - return `${parentIri}_${label}_${hash}`; - } -} - -// RdfData -export class RdfData { - protected _iri: string; // the subjects iri - protected _selfStatement: N3.NamedNode; - protected _rdfType: string; // rdfs type uf the subject - protected _rootType: string; // owl:Class or owl:ObjectProperty - protected _domain: string; // For predicates only: rdfs domain; assuming there is only one (the first) - protected _range: string; // For predicates only: rdfs range; assuming there is only one (the first) - protected _exists: boolean; // exists in store - protected _availablePredicates: TlnPredicate[] = []; // For Resources as subject: all the available predicates eith its range - protected _statements = new Map(); // all the properties && their objects extracted from lists - protected _listStatements = new Map(); // For Resources/Subjects all the statements as lists - - constructor() { - } - static selfStatement(s: string, o:string) { - return new N3.Quad( - new N3.NamedNode(s), - new N3.NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - new N3.NamedNode(o)) - } - - - // protected Methods - - protected setStmt(property: string, objectIri:string, index?) { - if (!this._statements.has(property)) {// if there is not yet a key for that property - this._statements.set(property, new TlnStatements(property)) - } - this._statements.get(property).setObject(objectIri, index); - } - - // returns whether the thing is a resource or not - protected setResourceRootType() { - this._rootType = 'http://www.w3.org/2002/07/owl#Class'; + return this._checkedOut; } - // returns whether the thing is a predicate or not - protected setPredicateRootType() { - this._rootType = 'http://www.w3.org/2002/07/owl#ObjectProperty'; - } - - // public methods - - public iri() { + iri() { return this._iri; } - public exists() { - return this._exists; - } - - public rdfType() { - return this._rdfType; - } - - public rootType() { - return this._rootType; - } - - public domain() { - return this._domain; - } - - public range() { - return this._range; - } - - // returns whether the thing is a resource or not - isResource(): boolean { - return this._rootType === 'http://www.w3.org/2002/07/owl#Class'; - } - - // returns whether the thing is a predicate or not - isPredicate(): boolean { - return this._rootType === 'http://www.w3.org/2002/07/owl#ObjectProperty'; - } - - public selfStatement() { - return this._selfStatement; - } - - public availablePredicates(): TlnPredicate[] { - return this._availablePredicates; - } - - public hasStatement(property: string) { - return this._statements.get(property) !== undefined && this._statements.get(property) !== null; - } - - public hasStatementEntries(property) { - return this._statements.get(property).objects().length > 0; - } - - //Todo: delete? - public getStatementIdx(property: string, iri: string) { - return this._statements.get(property).objects().findIndex(obj => obj === iri) - } - - public statements() { - return this._statements; - } -} - - -// RdfStoredData represents the data which is stored in the triple store. It gets and contains then all necessary data for any given iri -// from the triple store. -export class RdfStoredData extends RdfData{ - - constructor(private qService: QueryService) { - super(); - } - // async rdfDataFactory, execution waits until the instance is ready - static async rdfDataFactory(queryService: QueryService, iri) { - const rdfStoredData = await new RdfStoredData(queryService); - if (!await rdfStoredData.askIfInStore(iri)) {return null} // guard if iri not living in store - await rdfStoredData.setExistingRdfData(iri); - return rdfStoredData; - } - - // wrapper method for constructing the data - private async setExistingRdfData(iri: string) { - this._iri = iri; - this._exists = true; - this._selfStatement = new N3.NamedNode(iri); - if (await this.askIfResource(iri)) { // if it is a resource - await this.setResource(iri) } - else if (await this.askIfPredicate(iri)) { // if it is a predicate - await this.setPredicate(iri) } - } - - private async setResource(iri: string) { - this.setResourceRootType(); // root type of instances is always owlClass - this._rdfType = await this.getRdfTypeFromStore(iri); - this._selfStatement = RdfData.selfStatement(iri, this._rdfType) - this._availablePredicates = await this.getAvailablePredicatesFromStore(this._rdfType); - this.setStatementsFromStore(this._availablePredicates); - } - - private setPredicate(predicateIri: string) { - this.setPredicateRootType(); - this._rdfType = predicateIri; - this.getDomainFromStore(predicateIri).then(domain => this._domain = domain); - this.getRangeFromStore(predicateIri).then(range => this._range = range) - } - - // sets all statements for all given predicates as ttl list - private setAllListStatementsFromSore(predicates: TlnPredicate[]){ - predicates.forEach(p => { - if (p.takesList()) { - this.setListStatement(p.iri); - } - }) + public rdfTypeInStore(): string { + return this.resource._rdfDatasetFromStore.rdfType(); } - private setStatementsFromStore(availablePredicates?: TlnPredicate[]) { - availablePredicates.forEach(p => { - if (p.takesList()) { // if the predicate expects a list as object - this.setListStatement(p.iri); - } else {console.log('my property has range: ', p.iri, p.range)} // Todo: set for other cases + // sets itself to deleted, deletes all statements and runs deletion of each child + setDeleted() { + this.deleted = true; + this.resource.setDeleted(); // set the whole resource to deleted = true + if (!this.childResources || !this.childResources.length) {return} // guard; else deletes also all children recursively + this.childResources.forEach(child => { + child.setDeleted(); // run deletion also for children }); } - // sets all the statements of a given predicate as list to _statementsAsTtlList - private setListStatement(predicateIri: string) { - this.getListDataFromStore(predicateIri).then( - list => this._listStatements.set(predicateIri, list.toString() )); - } - - // asks wether an iri is an instance of a Class - private async askIfResource(iri: string): Promise { - const query = this.qService.parametrizeQueryWithItem(RQ_ASK_IF_CLASS_INSTANCE, iri); - return await this.qService.getData(EDITINGURL, query, 'ASK') - .then(isResource => (isResource['boolean'])); - } - - private async askIfPredicate(iri: string): Promise { - const query = this.qService.parametrizeQueryWithItem(RQ_ASK_IF_PROPERTY, iri); - return await this.qService.getData(EDITINGURL, query, 'ASK') - .then(isPredicate => (isPredicate['boolean'])); - } - - private async getRdfTypeFromStore(iri: string) { - const query = this.qService.parametrizeQueryWithItem(RQ_RDF_Type, iri); - return await this.qService.getData(EDITINGURL, query, 'SELECT') - .then( result => result['results']['bindings'].map(p => p['rdf_type'].value)[0]) - } - - private async getDomainFromStore(owlPropertyIri: string) { - const query = this.qService.parametrizeQueryWithItem(RQ_GET_DOMAIN, owlPropertyIri); - return await this.qService.getData(EDITINGURL, query, 'SELECT') - .then(result => result['results']['bindings'].map(p => p['domain'].value)[0]) - } - - private async getRangeFromStore(owlPropertyIri: string) { - const query = this.qService.parametrizeQueryWithItem(RQ_GET_RANGE, owlPropertyIri); - return await this.qService.getData(EDITINGURL, query, 'SELECT') - .then(result => this._range = result['results']['bindings'].map(p => p['range'].value)[0]) - } - - private async getAvailablePredicatesFromStore(rdfType: string): Promise { - const query = this.qService.parametrizeQueryWithItem(RQ_AVAILABLE_PROPERTIES, - '' , '', rdfType); - return await this.qService.getData(EDITINGURL, query, 'SELECT').then( - result => { - return result['results']['bindings'].map(p => new TlnPredicate(p['prop'].value, p['range'].value) ); - } - ) - } - - // gets the list as ttl for a given predicate - private getListDataFromStore(propertyIri: string): Promise { - let query = this.qService.parametrizeQueryWithItem(RQ_CONSTRUCT_LISTS, this._iri, propertyIri); - return this.qService.getData(EDITINGURL, query, 'CONSTRUCT' ); - } - - // public methods - - public async askIfInStore(iri: string): Promise{ - const query = this.qService.parametrizeQueryWithItem(RQ_ASK_EXISTS_IN_STORE, iri); - return await this.qService.getData(EDITINGURL, query, 'ASK') - .then(exists => (exists['boolean'])); - } - - public async exportAsEditorData(): Promise { - const editorData = new RdfEditorData(this._iri, this._rdfType, this._rootType, this._exists, this._domain, this._range); - await editorData.resetAvailablePredicates(this._availablePredicates); - await editorData.resetListStatements(this._listStatements); - await editorData.resetStatements(this._statements); - return editorData; - } -} - - -// RdfEditorData Contains the rdf data to edit which will be stored in the end -export class RdfEditorData extends RdfData { - - // _listsAsStatements = new Map(); // all the list entries as individual statements - constructor(iri: string, rdfType: string, rootType: string, exists = false, domain?: string, range?: string) { - super(); - this._iri = iri; - this._rdfType = rdfType; - this._rootType = rootType; - this._exists = exists; - this._domain = domain; - this._range = range; - } - - // public methods - - // makes setStm() available to RdfEditorData Instances - public setStatement(property: string, objectIri:string, index?) { - this.setStmt(property, objectIri, index); - } - - public deleteStatement(property: string, index: number) { - if (this.hasStatement(property) && this.hasStatementEntries(property)) { - this._statements.get(property).deleteObject(index); - } - } - - public moveObjectInStatement(property: string, previousIndex, newIndex) { - this._statements.get(property).moveObject(previousIndex, newIndex); - } - - public createStatementsFromLists() { - this._listStatements.forEach((value, key )=> { - this.setStatementsFromList(key); - }) - } - - // adds proper statements from a list - public setStatementsFromList(predicate){ - const parser = new N3.Parser(); - parser.parse(this._listStatements.get(predicate).toString(), - (error, quad) => { - if (quad && quad.object.termType === 'NamedNode' && quad.object.value) { - console.log('setting statement: ', predicate, ' ', quad); - //setting statements - this.setStmt(predicate, quad.object.value); - } - }); - } - - // sets or resets all _availablePredicates - public resetAvailablePredicates(predicates: TlnPredicate[] = []) { - this._availablePredicates = predicates; - } - - // sets or resets all _statements - public resetStatements(statements: Map = new Map()) { - this._statements = statements; - } - - // sets or resets all _statements - public resetListStatements(listStatements: Map = new Map()) { - this._listStatements = listStatements; - } - - public createListFromStatements() { - } - - public createListsFromStatements() { + setUndeleted() { + this.deleted = false; + this.resource.setUndeleted(); + if (!this.childResources || !this.childResources.length) {return} // guard; else deletes also all children recursively + this.childResources.forEach(child => { + child.setUndeleted(); // run also for children + }); } } -// The main super class for every thing which has to be added/edited or deleted in the triple store -export class TlnResource extends CrossRefEditorElement{ - iri: string; - _rdfDatasetFromStore: RdfStoredData; - _rdfDatasetToEdit: RdfEditorData; - _rdfDatasetToDepricate: RdfEditorData; - parentIri: string; - parentLabel: string; - private _inStore: boolean; // exists in Store or in memory/JS only - private _depricated: boolean; - private _edited: boolean; // whether an existing thing has been edited or not - private _deleted: boolean; // whether a thing has been deleted in the frontend - private _depricatedBy: string; // user who deleted/exchanged/depricated - private _depricatedAt: string; // timestamp of deletion - childsPredicate: string; // the predicate to join children - children: any[] = []; - createChildren: boolean; // whether children will be created or not - _createChild: EventEmitter; - _childToCreate: Subscription; // subscribing to _creatChild of parent - - constructor(public qService: QueryService, - label: string, - existingIri: string, - createChildren = false, - rdfsType?: string, - parentIri?: string, - parentLabel?: string, - checkedOut = false) { - super(); - this.createChildren = createChildren; - this._createChild = new EventEmitter(); - - if (existingIri && existingIri !== '') { // if we like to edit sth existing in the store. - this.loadExistingThing(existingIri); - } else {// for newly generated things which are not yet existing in the store - this.iri = CrossRefEditorElement.createIri(label, parentIri); - this._inStore = false; - } - this.parentIri = parentIri; - this.parentLabel = parentLabel; - this.checkOut(checkedOut); - } - -// loads ab existing resource or predicate - async loadExistingThing(existingIri: string){ - this._inStore = true; - this.iri = existingIri; // TODO needed? - await RdfStoredData.rdfDataFactory(this.qService, existingIri).then( - rdfStoredData => { - this._rdfDatasetFromStore = rdfStoredData; - // creating the rdfDataToEdit from the data in store - rdfStoredData.exportAsEditorData().then(editorData => { - this._rdfDatasetToEdit = editorData; - }); - } - ); - - } - - - // creates/populates all statements from store data for a given property - only available for existing things loaded from store - async getAllChildrenFromStore() { - // get all available properties - this._rdfDatasetFromStore.availablePredicates().forEach(predicate => { - if (predicate.takesList()) { - // if the range of the predicate is a list ... - if (this.createChildren) { - // creating children, i.e. emitting the iri of the child, so the subsciption will create those for us. - this._createChild.emit(predicate.iri); - } - if ((predicate.range !== 'http://www.w3.org/1999/02/22-rdf-syntax-ns#List')) { - console.log('predicate.range', predicate.range); - // it is a NamedNode, so we set the statements - // Todo: get the objects data and setStatement - //this.setStatement(predicate, quad.object.value); - // and create new children if needed - - } - - } - - }); - } - - - moveChildInChildren(previousIndex: number, newIndex: number) { - moveItemInArray(this.children, previousIndex, newIndex); - this._rdfDatasetToEdit.moveObjectInStatement(this.childsPredicate, previousIndex, newIndex); - } - - public addChild(child: any, index?){ - this.children.push(child); - //this.setStatement(this.childsPredicate, this.iri, index); - } - - isInStore() { - return this._inStore; - } - - isEdited() { - return this._edited; - } - - isDepricated() { - return this._depricated; - } - - isDeleted() { - return this._deleted; - } - - rdfDatasetFromStore() { - return this._rdfDatasetFromStore; - } - - rdfDatasetToDepricate() { - return this._rdfDatasetToDepricate; - } - - depricate(iri: string) { - // Todo: TimeStamp concat to iri - const newIri: string = iri.concat(iri, 'depricated'); - this._depricatedBy = new N3.Quad(); // user - this._depricated = true; - } - - // marks an existing thing as deleted and depricates it in the rdf world - deleteExisting(iri: string) { - this.depricate(iri); - this._deleted = true; - } - - // resets the editorData with the data gathered from the triple store - async resetFromStore(){ - this._rdfDatasetToEdit = await this._rdfDatasetFromStore.exportAsEditorData(); - } - - // returns all statements of a thing as n3.Quad[]; - // (properties with object) as n3 quad array in which there are both: the usual spo as well as a list containing all o's - getStatementsAsQuads(): N3.Quad[] { - let quads: N3.Quad[] = []; - if (this._rdfDatasetToEdit.statements().size > 0) { - this._rdfDatasetToEdit.statements().forEach((stm) => { - // adding objects to a list - quads.push(new N3.Quad(this._rdfDatasetToEdit.iri(), stm.property(), new N3.Writer().list(stm.objects()))); // list - // usual spo - stm.objects().forEach(object => { - // if there is an iri created for the thing, i.e. it is a new thing, we create new things - // TODO: Ontlogy - if (this.iri && stm.property() && object) { - quads.push(new N3.Quad(new N3.Term(this.iri), stm.property(), object)); - } - }); - }); - } - return quads; - } +export class TextGenesis extends CrossRefEditorElement{ - // returns the thing with all its statements as N3-quads/lists - quads() { - let quads: N3.Quads[] = [].concat(this.getStatementsAsQuads()); // concadinating so never undefined - // Sometimes there is none e.g. an existing page is part of identifiesAsVersion so we don't have to create iris & properties - // TODO Ontology - if (this._rdfDatasetToEdit.selfStatement() !== undefined && this._rdfDatasetToEdit.selfStatement() !== null) { - quads.splice(0,0, this._rdfDatasetToEdit.selfStatement()); - } - return quads; - } -} - -export class TextGenesis extends TlnResource{ - childsPredicate = 'http://www.nie.org/ontology/nietzsche#hasGeneticOrder'; - private _textVersions: TextVersion[] = []; + public childResources: GuiTextVersion[] = []; // setting childresources to GuiTextVersion[] + // for writing out ttl allQuads: N3.Quad[]; public ttl_export: string; public ttl_exportReady: EventEmitter; - constructor(qService: QueryService, - label: string, - existingIri: string = '', - createChildren = true, - rdfsType?: string, - parentIri?: string, - parentLabel?: string, - checkout = false) { - super(qService, label, existingIri, createChildren, rdfsType, parentIri, parentLabel, checkout); - console.log('createChildren', createChildren) - // const textVersionsList = new TlnList('http://www.nie.org/ontology/nietzsche#hasGeneticOrder', textVersions.map(t => t.iri)); - // this.statements.set('hasGeneticOrder', textVersionsList); - // this.rdfsType = 'http://www.nie.org/ontology/nietzsche#TextGenesis'; // used in TlnTextGenesisThing-methods - - if (this.isInStore()) { - this._childToCreate = this._createChild.subscribe(childIriToCreate => { - // creating an existing TextVersion - console.log('childIriToCreate', childIriToCreate) - let textVersion = new TextVersion(this.qService, childIriToCreate, this.iri); - this.addTextVersion(textVersion); - }); - } + constructor(iri: string, label?: string, parentIri?: string, checkOut = false, parentLabel?: string) { + super(iri, label, parentIri, checkOut); + const ranges = new Set(); + ranges.add('http://www.w3.org/1999/02/22-rdf-syntax-ns#List'); + this.childsPredicate = new TlnPredicate('http://www.nie.org/ontology/nietzsche#hasGeneticOrder', ranges); this.allQuads = []; this.ttl_exportReady = new EventEmitter(); } - public addTextVersion(textVersion: TextVersion, index?){ - this._textVersions.push(textVersion); - this._rdfDatasetToEdit.setStatement(this.childsPredicate, textVersion.iri, index); + public static async buildAsyncFromStore(qService: QueryService, iri: string): Promise { + let textGenesis = new TextGenesis(iri, 'Textgenese'); + textGenesis.resource = await TlnResource.buildFromStoreAsync(qService, iri, 'TextGenesis'); + let children = await textGenesis.resource.getChildIris(textGenesis.childsPredicate.iri()); + if (!children) {return textGenesis} // guard; else we create children + children.forEach((child, index) => { + GuiTextVersion.buildAsyncFromStore(qService, child, iri).then(guiTV => textGenesis.childResources.splice(index,0, guiTV)) + }); + return textGenesis; } - public textVersions(): TextVersion[] { - return this._textVersions; + public textVersions(): GuiTextVersion[] { + return this.childResources; } - setTextVersion(textVersion: TextVersion) { - const idx = this.getTextVersionIndex(textVersion.iri); - if (this._textVersions[idx].isCheckedOut()) { - this._textVersions[idx] = textVersion; - this._rdfDatasetToEdit.setStatement(this.childsPredicate, textVersion.iri, idx); + public addTextVersion(textVersion: GuiTextVersion, index: number = this.childResources.length){ + this.childResources.splice(index, 0, textVersion); + this.resource._rdfDatasetToEdit.putStatementInListAsStatement(this.childsPredicate, textVersion.iri(), index); + } + + public setTextVersion(textVersion: GuiTextVersion) { + const idx = this.getTextVersionIndex(textVersion.iri()); + if (this.childResources[idx].isCheckedOut()) { + this.childResources[idx] = textVersion; + this.resource._rdfDatasetToEdit.setStatementInListAsStatement(this.childsPredicate, textVersion.iri(), idx); } } + deleteTextVersion(iri) { + const idx = this.childResources.findIndex(tUnit => tUnit.iri() === iri); + // setting both to deleted - no matter whats the type + this.childResources[idx].onDelete(); + this.resource._rdfDatasetToEdit.deleteAllStatementsWithObject(iri) + } + + unDeleteTextVersion(iri) { + const idx = this.childResources.findIndex(tUnit => tUnit.iri() === iri); + this.childResources[idx].unDelete(); + this.resource._rdfDatasetToEdit.putStatementInListAsStatement(this.childsPredicate, iri, idx); + } getTextVersionIndex(iri: string): number { - return this.textVersions().findIndex(t => t.iri === iri) + return this.textVersions().findIndex(t => t.iri() === iri) } // CHecks out one TextVersion and unchecks all others. checkoutTextVersion(iri: string) { let idx = this.getTextVersionIndex(iri); - for (let i = 0; i <= this._textVersions.length-1; i++) { - if (i === idx) { this._textVersions[i].checkOut(true); } else { - this._textVersions[i].checkOut(false); + for (let i = 0; i <= this.childResources.length-1; i++) { + if (i === idx) { this.childResources[i].checkOut(true); } else { + this.childResources[i].checkOut(false); } } } - checkedOutTextVersion(): TextVersion { - return this._textVersions[this.checkedOutTextVersionIdx()]; + checkedOutTextVersion(): GuiTextVersion { + return this.childResources[this.checkedOutTextVersionIdx()]; } checkedOutTextVersionIdx(): number { - return this._textVersions.findIndex(tVersion => tVersion.isCheckedOut()); - } - - deleteTextVersion(iri: string) { - const delIndex = this._textVersions.findIndex(tVersion => tVersion.iri === iri); - this._textVersions.splice(delIndex, 1); - this._rdfDatasetToEdit.deleteStatement('hasGeneticOrder', delIndex) - } - - // returns an array of all quads contained - async getQuads(): Promise { - this.allQuads = []; - // adding the textGenesis quads - const textGenesisQuads: N3.Quad[] = this.quads(); - this.allQuads = await [... this.allQuads, ... textGenesisQuads] - // adding the textVersion quads - await this.textVersions().forEach(tVersion => { - this.allQuads = [...this.allQuads, ...tVersion.internalTextVersion.quads()]; - tVersion.internalTextVersion.textUnits().forEach(tUnit => { - if (!tUnit.wholePage) { - // only if it is not a whole page, i.e. there is a textUnit we get also its quads & its properties as quads TODO: Ontology - this.allQuads = [...this.allQuads, ...tUnit.quads()]; - } - }); - }); - return this.allQuads; - } + return this.childResources.findIndex(tVersion => tVersion.isCheckedOut()); + } + + // // returns an array of all quads contained + // async getQuads(): Promise { + // this.allQuads = []; + // // adding the textGenesis quads + // const textGenesisQuads: N3.Quad[] = await this.resource.buildRdfTransaction(); + // this.allQuads = await [... this.allQuads, ... textGenesisQuads] + // // adding the textVersion quads + // await this.textVersions().forEach(async tVersion => { + // let versionQuads = await tVersion.internalTextVersion.resource.buildRdfTransaction(); + // this.allQuads = [...this.allQuads, ...versionQuads]; + // tVersion.internalTextVersion.guiTextUnits().forEach(async tUnit => { + // if (!tUnit.wholePage) { + // let textUnitQuads = await tUnit.textUnit.resource.buildRdfTransaction(); + // // only if it is not a whole page, i.e. there is a textUnit we get also its quads & its properties as quads TODO: Ontology + // this.allQuads = [...this.allQuads, ...textUnitQuads]; + // } + // }); + // }); + // return this.allQuads; + // } // check for completeness isComplete() { return this.textVersions().every( tVersion => tVersion.isComplete()); } } -export class TextVersion extends CrossRefEditorElement{ - - iri: string; +export class GuiTextVersion extends CrossRefEditorElement{ label: string; - parentIri: string; // the textGenesis it belongs to - parentLabel: string; - isExternalResource: boolean; + private isExternalResource: boolean; + private changedExternity: boolean; internalTextVersion: InternalTextVersion; // the internal TextVersion Resource with its properties from RDF externalTextVersion: ExternalTextVersion; // the internal TextVersion Resource with its properties from RDF - constructor(qService: QueryService, - iri: string, // iri of internal or external resource + constructor(iri: string, // iri of internal or external resource + label: string, parentIri: string, - inStore: boolean = false, - isExternalResource: boolean = false, - label?: string, - parentLabel?, - checkedOut = false) { - super(); + checkOut = false, + inStore = false, + isExternalResource = false, + parentLabel?) { + super(iri, label, parentIri, checkOut); - if (iri !== '') { - this.iri = iri; - } else {// any random iri - does not matter - this.iri = CrossRefEditorElement.createIri(label, parentIri); } this.isExternalResource = isExternalResource; + this.changedExternity = false; this.label = label; - this.parentIri = parentIri; this.parentLabel = parentLabel; - this.checkOut(checkedOut); - // creating both no matter what it is, so switching in the editing process is possible - this.externalTextVersion = new ExternalTextVersion(qService, label, iri, false, '', parentIri, parentLabel); - this.internalTextVersion = new InternalTextVersion(qService, label, iri, true, '', parentIri, parentLabel); + } + + public static async buildAsyncFromStore(qService: QueryService, iri: string, parentIri?: string): Promise { + let guiTextVersion = new GuiTextVersion(iri, 'TextVersion', parentIri); + guiTextVersion.resource = await TlnResource.buildFromStoreAsync(qService, iri, 'TextVersion'); + // setting external + guiTextVersion.isExternalResource = guiTextVersion.rdfTypeInStore() === 'http://www.nie.org/ontology/nietzsche#ExternalTextUnit'; + if (guiTextVersion.isExternalResource) { + guiTextVersion.externalTextVersion = await ExternalTextVersion.buildAsyncFromStore(qService,iri, parentIri); + // creating also an internal one, but set it to deleted for switching easily + guiTextVersion.internalTextVersion = new InternalTextVersion('', 'Textversion', parentIri); + guiTextVersion.internalTextVersion.setDeleted() // set the resource to deleted because it should be ignored if not switched to internal + } else { + guiTextVersion.internalTextVersion = await InternalTextVersion.buildAsyncFromStore(qService, iri, parentIri); + // building also a new exteral one: and set it to deleted + guiTextVersion.externalTextVersion = new ExternalTextVersion('', 'TextVersion', parentIri); + guiTextVersion.externalTextVersion.setDeleted(); // set to deleted because we like to ignore it unless switched to externaö + } + return guiTextVersion; + } + + public static buildNew(parentIri: string) { + let guiTextVersion = new GuiTextVersion('', 'Textversion', parentIri, true); + guiTextVersion.externalTextVersion = new ExternalTextVersion(guiTextVersion.iri(), 'TextVersion', parentIri); + guiTextVersion.internalTextVersion = new InternalTextVersion(guiTextVersion.iri(), 'Textversion', parentIri ); + return guiTextVersion; + } isComplete() { if (this.isExternalResource) { return this.externalTextVersion.isComplete() } else { return this.internalTextVersion.isComplete(); } } -} -export class InternalTextVersion extends TlnResource{ - children: TextUnit[] = []; - _pagesCollection: TlnEntity[]= []; + changeExternity(isExternal: boolean) { + this.isExternalResource = isExternal; + if (isExternal) { // changed to external + // change deletions + this.internalTextVersion.setDeleted(); + if (this.externalTextVersion.resource.isInStore()) { + this.externalTextVersion.setUndeleted(); + } + this.externalTextVersion.checkOut(true); + this.internalTextVersion.checkOut(false); + } else { // chenged to internal + this.externalTextVersion.setDeleted(); + if (this.internalTextVersion.resource.isInStore()) { + this.internalTextVersion.setUndeleted() + } + this.internalTextVersion.checkOut(true); + this.externalTextVersion.checkOut(false); + } + this.changedExternity = !this.changedExternity; + } + + changeWholePage(textUnit: TextUnit, truthVal: boolean) { + this.internalTextVersion.checkedOutTextUnit().setWholePage(truthVal); + const idx = this.internalTextVersion.getGuiTextUnitIdxByIri(textUnit.iri()); + let predicate = this.internalTextVersion.childsPredicate; + // If wholePage Changes we have to change the the list statements because of the ontology: + if (truthVal) { // If wholePage = true, there is the page iri to store in the list + // resetting link to child + this.internalTextVersion.resource._rdfDatasetToEdit.setStatementInListAsStatement(predicate, textUnit._belongsToPage, idx); + // setting wholePage of child + //this.internalTextVersion.childResources[idx].setWholePage(truthVal) + } else { + // resetting link to child + this.internalTextVersion.resource._rdfDatasetToEdit.setStatementInListAsStatement(predicate, textUnit.iri(), idx); } + //console.log('changing wholePage ', textUnit.iri(), truthVal) + //this.internalTextVersion.childResources[idx].setWholePage(truthVal); + } + + // simply set everything to deleted - no matter if in use or not + onDelete() { + this.setDeleted(); + this.internalTextVersion.setDeleted(); + this.externalTextVersion.setDeleted(); + } + unDelete() { + this.setUndeleted(); + if (this.isExternalResource) { + this.externalTextVersion.setUndeleted(); + } else { + this.internalTextVersion.setUndeleted(); + } + } - constructor(qService: QueryService, - label: string, - existingIri: string, - createChildren = true, - rdfsType?: string, + hasChangedExternity(): boolean { + return this.changedExternity; + } +} + +export class InternalTextVersion extends CrossRefEditorElement{ + childResources: GuiTextUnit[] = []; + _pagesCollection: TlnCrossrefPage[]= []; + + constructor(iri: string, + label?: string, parentIri?: string, - parentLabel?: string, - checkOut = false, - textUnits: TextUnit[] = []) { - super(qService, label, existingIri, createChildren, rdfsType, parentIri, parentLabel, checkOut); - // this.rdfsType = 'http://www.nie.org/ontology/nietzsche#IdentifiedTextVersion'; - this.childsPredicate = 'http://www.nie.org/ontology/nietzsche#identifiesAsVersion'; - this.children = textUnits; - const textUnitList = new TlnStatements(this.childsPredicate, textUnits.map(t => t.iri)); - // this.setSelfOfTextVersion(); - if (this.isInStore()) { - console.log('textVersion in store', existingIri) - this._childToCreate = this._createChild.subscribe(childIriToCreate => { - console.log('child to create: ', childIriToCreate); - let textUnit = new TextUnit(this.qService,'TextUnit', childIriToCreate, this.iri ) - this.setTextUnit(textUnit); - this.addChild(textUnit); + checkOut = false) { // iri?: string, label?: string, parentIri?: string, checkOut = false + super(iri, label, parentIri, checkOut); + const ranges = new Set(); + ranges.add('http://www.w3.org/1999/02/22-rdf-syntax-ns#List'); + this.childsPredicate = new TlnPredicate('http://www.nie.org/ontology/nietzsche#identifiesAsVersion', ranges); + } + + // builds an internalTextVersion + public static async buildAsyncFromStore(qService: QueryService, iri: string, parentIri?: string): Promise { + let intVersion = new InternalTextVersion(iri, 'Interne TextVersion', parentIri); + intVersion.resource = await TlnResource.buildFromStoreAsync(qService, iri, 'TextVersion'); + let children = await intVersion.resource.getChildIris(intVersion.childsPredicate.iri()); + if (!children || children.length === 0 ) {return intVersion} // guard + children.forEach((child, index) => { + GuiTextUnit.buildAsyncFromStore(qService, child, 'Texteinheit', parentIri).then( guiTextUnit => { + intVersion._pagesCollection.splice(index,0, guiTextUnit.pageData); // adding/changing _pagescollection + intVersion.childResources.splice(index, 0, guiTextUnit); }); } + ); + return intVersion; } + public static async buildNew(iri: string, label?: string, parentIri?: string, checkout = true) { + let intVersion = new InternalTextVersion(iri, label, parentIri, checkout); + return intVersion; + } - textUnits(): TextUnit[] { - return this.children; + guiTextUnits(): GuiTextUnit[] { + return this.childResources; } hasTextUnits(): boolean { - return this.textUnits().length > 0; + return this.guiTextUnits().length > 0; } - - pagesCollection() { - return this._pagesCollection; + async getPagesCollection() { + let pagesCollection: TlnCrossrefPage[] = []; + for (let unit of this.guiTextUnits()) { + pagesCollection.push(unit.pageData); + } + return pagesCollection; } - // overwrites an existig textUnit or simply adds it + // overwrites an existig textUnit or simply adds it depending on the delete // to _pages, _versions, and to the statements - setTextUnit(textUnit: TextUnit, index?) { - if (!index) {index = this.children.length} // if no index passed it will be added at the end - this._pagesCollection.splice(index,0, textUnit.navItem.tlnEntity); - this.children.splice(index, 0, textUnit); - let identifiesAsVersionEntry: string; - if (textUnit.wholePage) { - identifiesAsVersionEntry = textUnit.belongsToPage; } else { - identifiesAsVersionEntry = textUnit.iri } - this._rdfDatasetToEdit.setStatement(this.childsPredicate, identifiesAsVersionEntry, index); + setTextUnit(guiTextUnit: GuiTextUnit, index) { + this._pagesCollection.splice(index,1, guiTextUnit.pageData); // changing _pagescollection + this.childResources.splice(index, 1, guiTextUnit); + this.resource._rdfDatasetToEdit.setStatementInListAsStatement(this.childsPredicate, guiTextUnit.iri(), index); } - deleteTextUnit(textUnitIri: string) { - const idx = this.children.findIndex(tUnit => tUnit.iri === textUnitIri); - this._pagesCollection.splice(idx, 1); - this.children.splice(idx,1); - this._rdfDatasetToEdit.deleteStatement(this.childsPredicate, idx); + // adds a new guiTextUnit + addNewGuiTextUnit(guiTextUnit: GuiTextUnit, index?: number) { + if (index === null || index === undefined) {index = this.childResources.length} // if no index passed it will be added at the end + // setting the list entry at the textVersion + this._pagesCollection.splice(index,0, guiTextUnit.pageData); // adding/changing _pagescollection + this.childResources.splice(index, 0, guiTextUnit); + this.resource._rdfDatasetToEdit.putStatementInListAsStatement(this.childsPredicate, guiTextUnit.iri(), index); } + deleteGuiTextUnit(textUnitIri: string) { + const idx = this.childResources.findIndex(tUnit => tUnit.iri() === textUnitIri); + this._pagesCollection[idx].deleted = true; + this.childResources[idx].setDeleted(); + this.childResources[idx].textUnit.setDeleted(); + // deleting also the statement connecting the textUnit + this.resource._rdfDatasetToEdit.deleteAllStatementsWithObject(textUnitIri); + } + + unDeleteGuiTextUnit(textUnitIri: string) { + const idx = this.childResources.findIndex(tUnit => tUnit.iri() === textUnitIri); + this._pagesCollection[idx].deleted = false; + this.childResources[idx].setUndeleted(); + this.childResources[idx].textUnit.setUndeleted(); + // resetting also the statement connecting the textUnit + this.resource._rdfDatasetToEdit.putStatementInListAsStatement(this.childsPredicate, textUnitIri, idx) + } + + // Todo: 1) Refactor so everything is in superclass; 2) remove children if not needed except onInit, remove pagesCollection if same moveTextUnitInArray(previousIndex: number, newIndex: number) { + console.log('HERE MOVING'); + moveItemInArray(this.childResources, previousIndex, newIndex); moveItemInArray(this._pagesCollection, previousIndex, newIndex); - this.moveChildInChildren(previousIndex, newIndex); - } + this.resource._rdfDatasetToEdit.moveObjectInListAsStatement(this.childsPredicate.iri(), previousIndex, newIndex); - setTextUnits(textUnits: TextUnit[]) { - this.children = textUnits; - // ... + console.log('storeData', this.resource._rdfDatasetFromStore.listsAsStatements()) } - getTextUnitIdxByIri(iri: string) { - return this.textUnits().findIndex(tUnit => tUnit.iri === iri); + getGuiTextUnitIdxByIri(iri: string) { + return this.guiTextUnits().findIndex(tUnit => tUnit.iri() === iri); } getTextUnitByIri(iri: string) { - const idx = this.getTextUnitIdxByIri(iri); - return this.children[idx]; + const idx = this.getGuiTextUnitIdxByIri(iri); + return this.childResources[idx]; } - checkedOutTextUnit(): TextUnit { - return this.children[this.checkedOutTextVersionIdx()]; + checkedOutTextUnit(): GuiTextUnit { + return this.childResources[this.checkedOutTextVersionIdx()]; } checkedOutTextVersionIdx(): number { - return this.children.findIndex(tUnit => tUnit.isCheckedOut()); + return this.childResources.findIndex(tUnit => tUnit.isCheckedOut()); } - checkOutTextUnit(iri: string):TextUnit { - let idx = this.children.findIndex(t => t.iri === iri); - for (let i = 0; i <= this.children.length-1; i++) { - if (i === idx) { this.children[i].checkOut(true); } else { - this.children[i].checkOut(false); + checkOutTextUnit(iri: string):GuiTextUnit { + let idx = this.childResources.findIndex(t => t.iri() === iri); + for (let i = 0; i <= this.childResources.length-1; i++) { + if (i === idx) { this.childResources[i].checkOut(true); } else { + this.childResources[i].checkOut(false); } } - return this.children[idx]; + return this.childResources[idx]; } // simple check for completeness isComplete(){ - return (this.textUnits().length && this.textUnits().every(u => - u.startLine() !== undefined && u.endLine() !== undefined || u.wholePage )); + return true; + //return (this.textUnits().length && this.textUnits().every(u => + // u.startLine() !== undefined && u.endLine() !== undefined || u.wholePage )); } } -export class ExternalTextVersion extends TlnResource{ - externalResourceHasUrl: string; +export class ExternalTextVersion extends CrossRefEditorElement{ + hasUrl: string; hasTitle: string; + hasTitleP: TlnPredicate; + hasUrlP: TlnPredicate; - constructor(qService: QueryService, - label: string, - existingIri: string, - createChildren = false, - rdfsType?, - parentIri?, - parentLabel?, - hasTitle?: string) { - super(qService, label, existingIri, createChildren, rdfsType, parentIri, parentLabel); - //this.rdfsType = 'http://www.nie.org/ontology/nietzsche#ExternalTextUnit'; - this.childsPredicate = 'http://www.nie.org/ontology/nietzsche#textUnitHasUrl'; - this.hasTitle = hasTitle; + constructor(iri?: string, + label?: string, + parentIri?: string, + checkOut = false ) { + super(iri, label, parentIri, checkOut); + this.hasTitleP = new TlnPredicate('http://www.nie.org/ontology/nietzsche#textUnitHasTitle', new Set(['http://www.w3.org/2001/XMLSchema#string'])); + this.hasUrlP = new TlnPredicate('http://www.nie.org/ontology/nietzsche#textUnitHasUrl', new Set(['http://www.w3.org/2001/XMLSchema#anyURI'])); + + } - // TODO: let generic properties of RdfEditClassdo the properties. + public static async buildAsyncFromStore(qService: QueryService, iri: string, parentIri): Promise { + let extVersion = new ExternalTextVersion(iri, 'TextVersion', parentIri); + extVersion.resource = await TlnResource.buildFromStoreAsync(qService, iri, 'Externe Textversion'); + extVersion.hasTitle = extVersion.resource.rdfDatasetFromStore().getFirstObject('http://www.nie.org/ontology/nietzsche#textUnitHasTitle'); + extVersion.hasUrl = extVersion.resource.rdfDatasetFromStore().getFirstObject('http://www.nie.org/ontology/nietzsche#textUnitHasUrl'); + return extVersion; } // simple check for basic completeness isComplete(){ - return this.externalResourceHasUrl && this.hasTitle + return this.hasUrl && this.hasTitle + } + + setTitle(title: string) { + this.resource._rdfDatasetToEdit.setStatement(this.hasTitleP, title, 0) + } + + setUrl(url: string) { + this.resource._rdfDatasetToEdit.setStatement(this.hasUrlP, url, 0) } } +// wrapper class +export class GuiTextUnit extends CrossRefEditorElement{ + textUnit: TextUnit; + wholePage: boolean; + deleted = false; + pageData: TlnCrossrefPage; // iri?: string, label?: string, parentIri?: string, checkOut = false, parentLabel?: string + + constructor(iri, label: string, parentIri?: string, checkOut = true, wholePage = true, parentLabel?: string, pageData?: TlnCrossrefPage) { + super(iri, label, parentIri, checkOut); + this.wholePage = wholePage; + this.parentLabel = parentLabel; + this.pageData = pageData; + //this.resource = new TlnResource(iri, 'TextUnit', 'http://www.nie.org/ontology/nietzsche#PartOfPageTextUnit', parentIri, parentLabel); + this.displayedPredicates = ['http://www.nie.org/ontology/nietzsche#belongsToPage']; // + } + + // async builder + static async buildAsyncFromStore(qService: QueryService, iri: string, label, parentIri?: string) { + let guiTextUnit: GuiTextUnit = new GuiTextUnit(iri, label, parentIri, false); + // in case the thing is a textUnit we like to know the page it belongs to - if it is a page, we need nothing but the iri itself + let stmDef = new ChangeMgmntDef(); // Todo: maybe whitelist/blacklist + // set the resource of our guiTextUnit: only to know what the thing actually is! + guiTextUnit.resource = await TlnResource.buildFromStoreAsync(qService, iri, 'Texteinheit', stmDef); + if (guiTextUnit.rdfTypeInStore() === 'http://www.nie.org/ontology/nietzsche#PartOfPageTextUnit') { + const pageIri = guiTextUnit.resource.rdfDatasetFromStore().getFirstObject('http://www.nie.org/ontology/nietzsche#belongsToPage'); + guiTextUnit.pageData = await TlnCrossrefPage.buildFromStoreAsync(qService, pageIri); + guiTextUnit.textUnit = await TextUnit.buildFromStoreAsync(qService, iri, guiTextUnit.pageData.lineData); + guiTextUnit.wholePage = false; // default is true, so we have to set it here + } + if (guiTextUnit.rdfTypeInStore() === 'http://www.nie.org/ontology/nietzsche#Page') { + // the thing is a page, the iri passed a pages iri, but we already add a new textUnit + // in every case so it can be switched easily to the already created one + guiTextUnit.pageData = await TlnCrossrefPage.buildFromStoreAsync(qService, iri); + guiTextUnit.textUnit = TextUnit.createNewTextUnit(guiTextUnit.pageData.pageIri, guiTextUnit.pageData.lineData, parentIri); + guiTextUnit.textUnit.setDeleted(); // set the textUnit to deleted hence there is no textUnit if it is wholepage + } + return guiTextUnit; + } -export class TextUnit extends TlnResource { - belongsToPage: string; - wholePage: boolean; // If true it IS an existing page in the List of "identifiesAsVersion" and there is no TextUnit to write! + // resets the wholePage boolean and changes the iri according to the chosen state (wholepage means that there is not a textUnit - + // it is a page) + setWholePage(truthVal: boolean) { + console.log() + this.wholePage = truthVal; + if (truthVal) { + // if wholePage === true then there is only a link to the page and no textUnit at all. So we set the iri to the pages Iri and the + // textUnit to deleted + this._iri = this.pageData.pageIri; + this.textUnit.setDeleted(); + } else { + this._iri = this.textUnit.iri(); + this.textUnit.setUndeleted(); + } + + } +} + +export class TextUnit extends CrossRefEditorElement { + _belongsToPage: string; private _startLine?: string; private _endLine?: string; lineData: Line[]; displayedLabel: string; displayedStartLine: number; displayedEndLine: number; selectedLines: string[]; - navItem?: NavigationEntity; - // _availableProperties = [ - // 'http://www.nie.org/ontology/nietzsche#belongsToPage', - // 'http://www.nie.org/ontology/nietzsche#startLine', - // 'http://www.nie.org/ontology/nietzsche#endLine']; + startLinePredicate: TlnPredicate; + endLinePredicate: TlnPredicate; + belongsToPagePredicate: TlnPredicate; - constructor(qService: QueryService, + constructor(iri: string, label:string, - existingIri: string = '', - createChildren, - rdfsType?: string, - parentIri?: string, - wholePage = true, + lineData: Line[], + parentIri: string = '', + checkOut = false, parentLabel?: string, - displayedLabel?: string, - navItem?: NavigationEntity, startLine?: string, endLine?: string, startLineNumber?: number, endLineNumber?: number) { - super(qService, label, existingIri, createChildren, rdfsType, parentIri, parentLabel); - // this.rdfsType= 'http://www.nie.org/ontology/nietzsche#PartOfPageTextUnit'; - this.belongsToPage = parentIri; - this.wholePage = wholePage; - this.displayedLabel = displayedLabel; - this.navItem = navItem; - this.getLineData(); + super(iri, label, parentIri, checkOut, parentLabel); + //this.rdfsType= 'http://www.nie.org/ontology/nietzsche#PartOfPageTextUnit'; + this.startLinePredicate = new TlnPredicate('http://www.nie.org/ontology/nietzsche#startLine', new Set(['http://www.w3.org/2001/XMLSchema#anyURI'])); + this.endLinePredicate = new TlnPredicate('http://www.nie.org/ontology/nietzsche#endLine', new Set(['http://www.w3.org/2001/XMLSchema#anyURI']) ); + this.belongsToPagePredicate = new TlnPredicate('http://www.nie.org/ontology/nietzsche#belongsToPage', new Set(['http://www.w3.org/2001/XMLSchema#anyURI'])) + this.lineData = lineData; this._startLine = startLine; this._endLine = endLine; this.selectedLines = []; this.setLines(startLine, startLineNumber, endLine, endLineNumber); - //this.statements.set('belongsToPage', new TlnStatements('http://www.nie.org/ontology/nietzsche#belongsToPage', [parentIri])); + } + + static async buildFromStoreAsync(qService: QueryService, iri, lineData: Line[]) { + let textUnit = new TextUnit(iri, 'Texteinheit', lineData ); + textUnit.resource = await TlnResource.buildFromStoreAsync(qService, iri); + textUnit._belongsToPage = textUnit.resource.rdfDatasetFromStore().getFirstObject(textUnit.belongsToPagePredicate.iri()); + textUnit._startLine = textUnit.resource.rdfDatasetFromStore().getFirstObject(textUnit.startLinePredicate.iri()); + textUnit.displayedStartLine = textUnit.getLineNumber(textUnit._startLine); + textUnit._endLine = textUnit.resource.rdfDatasetFromStore().getFirstObject(textUnit.endLinePredicate.iri()); + textUnit.displayedEndLine = textUnit.getLineNumber(textUnit._endLine); + textUnit.setSelectedLines(textUnit._startLine, textUnit._endLine); + return textUnit; + } + + // creates a new TextUnit which is not yet in store + static createNewTextUnit(pageIri: string, lineData: Line[], parentIri: string) { + let textUnit = new TextUnit('', 'Texteinheit', lineData, parentIri); + textUnit._belongsToPage = pageIri; + let a = new TlnPredicate('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'); + textUnit.resource._rdfDatasetToEdit.statements().putAgentStatement(a, 'http://www.nie.org/ontology/nietzsche#PartOfPageTextUnit'); + textUnit.resource._rdfDatasetToEdit.statements().putAgentStatement(textUnit.belongsToPagePredicate, pageIri); + return textUnit; + } + + // returns the lineNumber for a given lineIri + getLineNumber(lineIri: string) { + return this.lineData[this.lineData.findIndex(l => l.line === lineIri)].lNumber; } setLines(sLine: string, sLineNo: number, eLine: string, eLineNo: number){ - let startLineArr = []; - let endLineArr = []; - if (sLine) {startLineArr.push(sLine)} - if (eLine) {endLineArr.push(eLine)} - //this.statements.set('startLine', new TlnStatements('http://www.nie.org/ontology/nietzsche#startLine', startLineArr)); - //this.statements.set('endLine', new TlnStatements('http://www.nie.org/ontology/nietzsche#endLine', endLineArr)); if (sLine && sLineNo && eLine && eLineNo) { this.setStartLine(sLine, sLineNo); this.setEndLine(eLine, eLineNo); this.setSelectedLines(sLine, eLine); } } startLine() { return this._startLine } hasStartLine() { return this._startLine !== undefined && this._startLine !== null } endLine() { return this._endLine } hasEndLine() { return this._endLine !== undefined && this._endLine !== null } - // sets the select6ed lines for highlighting/passing to tln-page-view only - public setSelectedLines(startLine, endLine?) { + // sets the selected lines for highlighting/passing to tln-page-view only + public setSelectedLines(startLine: string, endLine?: string) { if (!endLine || endLine === undefined) { this.selectedLines = [this.startLine()] } else { let started = false; let ended = false; this.lineData.forEach(line => { if (line.line === startLine) { started = true; } if (line.line === endLine) { this.selectedLines.push(line.line); ended = true; } if (started && !ended) { this.selectedLines.push(line.line); } }); } } + // sets or resets the startLine depending on its existance setStartLine(startLine: string, lineNumber: number) { this._startLine = startLine; this.displayedStartLine = lineNumber; this.setSelectedLines(this.startLine()); - if (this.endLine()) {this.deleteEndLine();} - this._rdfDatasetToEdit.setStatement('startLine', startLine, 0); + if (this.endLine()) {this.deleteEndLine();}// resetting the startline must delete the selected endline + if (this.resource._rdfDatasetToEdit.hasStatementEntriesForPredicate(this.startLinePredicate.iri())) { + this.resource._rdfDatasetToEdit.setStatement(this.startLinePredicate, startLine, 0); + } else {this.resource._rdfDatasetToEdit.putStatement(this.startLinePredicate, startLine);} } setEndLine(endLine: string, lineNumber: number) { this._endLine = endLine; this.displayedEndLine = lineNumber; this.setSelectedLines(this.startLine(), endLine); - this._rdfDatasetToEdit.setStatement('endLine', endLine, 0); + if (this.resource._rdfDatasetToEdit.hasStatementEntriesForPredicate(this.endLinePredicate.iri())) { + this.resource._rdfDatasetToEdit.setStatement(this.endLinePredicate, endLine, 0); + } else {this.resource._rdfDatasetToEdit.putStatement(this.endLinePredicate, endLine);} } deleteEndLine() { this._endLine = null; this.displayedEndLine = null; - // this.deleteStatement('endline', 0) } +} + +export interface Line { + line: string; + lNumber: number; +} + +export class TlnCrossrefPage { + lineData: Line[]; + deleted = false; + constructor(public pageIri: string, public pageNumber?: Number, public thumb?: SafeUrl, public svg?: SafeUrl) { - setWholePage(truthVal: boolean) { - this.selectedLines = []; - this.wholePage = truthVal; - //TODO: change oder hide rdfData in case of wholePageChange } - // sets the lineData available for the checked out textUnit - getLineData() { - this.getLinesFromStore().then(lines => { - this.lineData = lines.map(line => { - return {line: line['line']['value'], lNumber: line['lNumber']['value']} + // async builder + public static async buildFromStoreAsync(qService: QueryService, pageIri: string): Promise { + const query = QueryService.bindVariableWithIri(PAGE_PREVIEW_RQ, 'page', pageIri); + let crossRefPage = await qService.getData(BASEURL, query,'SELECT') + .then( pageData => { + let page = pageData['results']['bindings'][0]; + let pageNumber = page['pageNumber'] && page['pageNumber']['value']? page['pageNumber']['value']: ''; + let thumb = page['thumb'] && page['thumb']['value']? page['thumb']['value']: ''; + let svg = page['svgUrl'] && page['svgUrl']['value']? page['svgUrl']['value']: ''; + return new TlnCrossrefPage(pageIri, pageNumber, thumb, svg); }); + crossRefPage.lineData = await TlnCrossrefPage.getLineData(pageIri, qService); + return crossRefPage; + } + // sets the lineData available for the checked out textUnit + public static async getLineData(pageIri: string, qService: QueryService) { + let lines = await TlnCrossrefPage.getLinesFromStore(pageIri, qService); + return lines.map(line => { + return {line: line['line']['value'], lNumber: line['lNumber']['value']} }); } - // TODO: Generalize/Parametrize - async getLinesFromStore(): Promise { - const query = await this.qService.parametrizeQueryWithItem(RQ_CROSSREF_TREE_LINES, this.belongsToPage, '', ''); - return await this.qService.getData(BASEURL, query, 'SELECT').then(res => { + public static async getLinesFromStore(pageIri: string, qService: QueryService): Promise { + const query = await qService.parametrizeWhereClauseWithItems(RQ_CROSSREF_TREE_LINES, pageIri, '', ''); + return await qService.getData(BASEURL, query, 'SELECT').then(res => { return res['results']['bindings']; }); } } -export class TlnStatements { - protected _property: N3.NamedNode; - protected _objects: N3.Term[]; - - constructor(property: string, objects: string[] = []) { - this._property = new N3.NamedNode(property); - this._objects = objects.map(val => new N3.NamedNode(val)); - } - - public async setObject(objectIri: string, index?) { - if (!index || index === undefined) { - index = this.objects().length +export const PAGE_PREVIEW_RQ = ` +PREFIX tln: +PREFIX rdf: +PREFIX xsd: +SELECT DISTINCT ?page ?pageNumber ?image ?svgUrl ?thumb WHERE { + ?page ?p ?o. + OPTIONAL { + ?page tln:hasNumber ?pageNumber. + ?page tln:hasSvgImage ?svg. + ?svg tln:hasPrimaryurl ?svgUrl. + ?page tln:hasFaksimileImage ?image. + ?image tln:hasFileName ?imgLabel; + tln:hasPrimaryurl ?primaryUrl; + tln:hasThumburl ?thumb; } - this._objects.splice(index, 0, new N3.NamedNode(objectIri)); - } - - // deletes a thing completely - public deleteObject(index) { - this._objects.splice(index, 1); - } - - public moveObject(previousIndex, newIndex) { - moveItemInArray(this._objects, previousIndex, newIndex); - } - - public addObjects(objectIris: string[]) { - objectIris.map(iri => new N3.NamedNode(iri)).forEach(object => this._objects.push(object)); - } - - public setObjects(objectIris: string[]) { - this._objects = objectIris.map(iri => new N3.NamedNode(iri)); - } - public property() { - return this._property; - } - - public objects() { - return this._objects; - } -} - -export interface Line { - line: string; - lNumber: number; -} - -export class TlnPredicate { - constructor(public iri: string, public range: string) { - } - - // returns whether a the range is a list or not - public takesList(): boolean { - return this.range ==='http://www.w3.org/1999/02/22-rdf-syntax-ns#List'; - } -} - + }`; diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.html b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.html index 906f472..c431d7d 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.html +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.html @@ -1,40 +1,40 @@ - + Textauswahl - + taskgesamte Seite ruleZeilenauswahl - +

Erfassen sie hier den Textbereich innerhalb der ausgewählten Seiten. Klicken Sie hierzu per Mausklick auf die entsprechende Zeilennummer der Start- und Endeile der Textversion.

-

- Startzeile: {{textUnit.displayedStartLine}}*

-

- Endzeile: {{textUnit.displayedEndLine}}*

- + Startzeile: {{guiTextUnit.textUnit.displayedStartLine}}* +

+ Endzeile: {{guiTextUnit.textUnit.displayedEndLine}}*

+
- +
GESAMTE SEITE
- +
diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.ts b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.ts index 1073d22..51278a6 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.ts +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/line-selector-component/line-selector-component.component.ts @@ -1,50 +1,50 @@ import {Component, OnChanges, forwardRef, Inject} from '@angular/core'; -import {CrossrefEditorDataServiceService, TextUnit} from '../../crossref-editor-data-service.service'; +import {CrossrefEditorDataServiceService, GuiTextUnit, TextUnit} from '../../crossref-editor-data-service.service'; import {TlnQueryServiceInterface} from '../../../tln-edition/models'; import {NavigationServiceService} from '../../../services/navigation-service.service'; import {MatButtonToggleChange} from '@angular/material'; import {NG_VALUE_ACCESSOR} from '@angular/forms'; import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; @Component({ selector: 'app-line-selector-component', templateUrl: './line-selector-component.component.html', styleUrls: ['./line-selector-component.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => LineSelectorComponentComponent), } ] }) export class LineSelectorComponentComponent implements OnChanges { // subscribe to page selections linesOfPages: Map; // The page data with all the lines // For passing into TlnPageView ... max_width: number; queryService: TlnQueryServiceInterface; preferPrimaryUrl = true; constructor(private naviService: NavigationServiceService, private dataService: CrossrefEditorDataServiceService, private dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public textUnit: TextUnit) { + @Inject(MAT_DIALOG_DATA) public guiTextUnit: GuiTextUnit) { this.linesOfPages = new Map(); - this.linesOfPages.set(this.textUnit.belongsToPage,this.textUnit.lineData); + this.linesOfPages.set(this.guiTextUnit.pageData.pageIri,this.guiTextUnit.pageData.lineData); this.max_width = window.innerWidth/1.5; } ngOnChanges() { } - public changeWholePage(textUnit, { value }: MatButtonToggleChange) { + public changeWholePage({ value }: MatButtonToggleChange) { const isTrue = (value === 'true'); - this.dataService.setWholePage(textUnit, isTrue); + this.dataService.changeWholePage(this.guiTextUnit.textUnit, isTrue); } closeDialog() { this.dialogRef.close(); } } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.html b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.html index fe4041e..026b2a1 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.html +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.html @@ -1,46 +1,59 @@

Ziehen Sie die Manuskriptseiten der Textversion von der Navigationsleiste links per Drag'n drop in das Feld "Texteinheiten". Die Reihenfolge der erfassten Seiten lässt sich ebenfalls per Drag'n drop ändern.

-
{{internalTextVersion._pagesCollection[0].iri}}
-
- Seiten check +
{{internalTextVersion.iri()}}
+
+ Seiten/Texteinheiten check * - - + + - {{textUnit.navItem.parentLabel}} - {{textUnit.displayedLabel}}: - gesamte Seite {{textUnit.displayedLabel}} - Zeilen {{textUnit.displayedStartLine}}-{{textUnit.displayedEndLine}} + Texteinheit S. {{unit.pageData.pageNumber}}: + (gesamte Seite) + Zeilen {{unit.textUnit.displayedStartLine}}-{{unit.textUnit.displayedEndLine}}
-
- +
+ +
- -
+
+ + + {{unit.iri()}} + + + + + +
-
hier ablegen ...
+
hier Seiten ablegen ...
diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.scss b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.scss index eb3ee2d..2034ec7 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.scss +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.scss @@ -1,63 +1,71 @@ .textversion-field { width: 60vh; } .droplist { border: solid 1px #ccc; min-height: 50vh; width: 70vh; background: white; border-radius: 4px; } .mat-list .mat-list-item { width: 90%; height: 100%; } .thumbnail { max-width: 60px; max-height: 60px; } .card-item { width: 100%; min-width: 100%; margin-top: 5px; background-color: rgba(255, 255, 255, 0) !important; cursor: grab; } +.card-item-deleted { + width: 100%; + min-width: 100%; + margin-top: 5px; + background-color: rgba(193, 193, 193, 0) !important; + cursor: grab; +} + .textversion-container{ display: table; } .textversion-content { display: table-cell; vertical-align: middle; } .interact-button{ border-color: #000000; margin: 1em; justify-content: right; } .delete-button{ justify-content: flex-end; } .obligatory { color: red; } .complete { color: green; } .placeholder { background: #ccc; border: dotted 3px #999; min-height: 12em; width:90%; } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.ts b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.ts index bbce134..54e403a 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.ts +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/page-collector-component/page-collector-component.component.ts @@ -1,82 +1,92 @@ import {Component, Input, OnChanges} from '@angular/core'; import {CdkDragDrop} from '@angular/cdk/drag-drop'; import {NavigationEntity, TlnEntity} from '../../../models/models'; import {NavigationServiceService} from '../../../services/navigation-service.service'; import {ActivatedRoute} from '@angular/router'; -import {CrossrefEditorDataServiceService, InternalTextVersion, TextVersion} from '../../crossref-editor-data-service.service'; +import { + CrossrefEditorDataServiceService, InternalTextVersion, GuiTextVersion, + TlnCrossrefPage +} from '../../crossref-editor-data-service.service'; import {TlnQueryServiceInterface} from '../../../tln-edition/models'; import {MatDialog} from '@angular/material/dialog'; import {LineSelectorComponentComponent} from '../line-selector-component/line-selector-component.component'; @Component({ selector: 'app-page-collector-component', templateUrl: './page-collector-component.component.html', styleUrls: ['./page-collector-component.component.scss'] }) export class PageCollectorComponentComponent implements OnChanges { @Input() instanceIdx: number; // the number of the instance @Input() internalTextVersion: InternalTextVersion; - @Input() pages: TlnEntity[] = []; + @Input() pages: TlnCrossrefPage[] = []; instanceId: string; // lines ... linesOfPages: Map; // The page data with all the lines // For passing to TlnPageView ... max_width: number; queryService: TlnQueryServiceInterface; preferPrimaryUrl = true; // selected start and endLine ... startLine: string; endLine: string; constructor(private naviService: NavigationServiceService, private activatedRoute: ActivatedRoute, private dataService: CrossrefEditorDataServiceService, public dialog: MatDialog) { this.linesOfPages = new Map(); } ngOnChanges() { this.instanceId = `crossref-editor-drop-list-${this.instanceIdx}`; if ( this.internalTextVersion.hasTextUnits() ){ - this.internalTextVersion.textUnits().forEach(tUnit => { - this.linesOfPages.set(tUnit.belongsToPage,tUnit.lineData) + this.internalTextVersion.guiTextUnits().forEach(tUnit => { + if (!tUnit.wholePage && tUnit.textUnit && tUnit.textUnit._belongsToPage){ + this.linesOfPages.set(tUnit.textUnit._belongsToPage,tUnit.textUnit.lineData) + } }); } this.max_width = window.innerWidth/2; } itemDropped(event: CdkDragDrop) { if (event.previousContainer === event.container) { this.dataService.movePageInArray(event.previousIndex, event.currentIndex) } else { - this.dataService.addPage(event.item.data, event.currentIndex); + this.dataService.onAddingPage(event.item.data, event.currentIndex); } } removeFromCollection(itemToRemove: string) { - this.dataService.removeTextUnit(itemToRemove); + this.dataService.onRemovePage(itemToRemove); + } + + unDelete(iri: string) { + this.dataService.undeleteGuiTextUnit(iri); } openLineSelectorDialog(textUnitIri): void { this.checkoutTextUnit(textUnitIri); + console.log('opening dialoge with ', this.internalTextVersion.checkedOutTextUnit()) const dialogRef = this.dialog.open(LineSelectorComponentComponent, { width: '90vw', height: '90vh', data: this.internalTextVersion.checkedOutTextUnit() }); dialogRef.afterClosed().subscribe(result => { console.log('The dialog was closed'); }); } checkoutTextUnit(textUnitIri) { this.dataService.textGenesis.checkedOutTextVersion().internalTextVersion.checkOutTextUnit(textUnitIri); } } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.html b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.html index 0c48a51..cb0024c 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.html +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.html @@ -1,97 +1,113 @@ - Textgenese: {{textGenesis.iri}} + Textgenese: {{textGenesis.iri()}} - - - {{textUnit.navItem.parentLabel}}: {{textUnit.displayedLabel}} - {{textUnit.displayedStartLine}}-{{textUnit.displayedEndLine}} - ; + + + {{textUnit.displayedLabel}}: {{textUnit.displayedLabel}} + {{textUnit.displayedStartLine}}-{{textUnit.displayedEndLine}} + ; -> {{tVersion.externalTextVersion.hasTitle}} (ext. Resource) - - - -
- - - {{i+1}}. Textversion: - - - - {{textUnit.navItem.parentLabel}}: {{textUnit.displayedLabel}} - {{textUnit.displayedStartLine}}-{{textUnit.displayedEndLine}} - ; +
+ + + +
+ + + {{i+1}}. Textversion: + + + + {{textUnit.pageData.pageNumber}} + : {{textUnit.displayedStartLine}}-{{textUnit.displayedEndLine}} + ; + + + (extern): {{textVersion.externalTextVersion.hasTitle}} + +
+
+ + {{textVersion.isCheckedOut()? '' : 'Panel ausklappen zum Bearbeiten ...'}} + +
+ + + + + Interne Textversion + + + Externe Textversion + + + + + - - externe Textversion: {{textVersion.externalTextVersion.hasTitle}} - -
- - - {{textVersion.isCheckedOut()? '' : 'Panel ausklappen zum Bearbeiten ...'}} - - - - - - - Interne Textversion - - - Externe Textversion - - - - - - - - - - + + + + +
- -
+ +
+ +
+ + + {{delTextVersion.iri()}} + + + + + +
+
{{textGenesis.ttl_export}} diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.scss b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.scss index 5771ed6..61cac56 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.scss +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.scss @@ -1,58 +1,65 @@ .list-container { margin: 20px; height: 100%; max-width: 75%; overflow: hidden; } .ext-radio-group { display: flex; flex-direction: column; margin: 15px 0; } .text-version { margin: 20px; padding: 20px 10px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); align-items: center; justify-content: space-between; height: auto !important; cursor: pointer; } .text_version_header { background-color: #dadada; } .mat-expansion-panel-header { padding: 1em; } .textversion-iri { margin-right: 2em; } +.text-version-deleted { + width: 100%; + min-width: 100%; + margin-top: 5px; + background-color: rgba(193, 193, 193, 0) !important; +} + .mat-expansion-panel-header-title { display: flex; justify-content: center; flex-direction: column; } .panel-icon { margin-left: 2em; vertical-align: middle; margin-right: 2em; } .action-button { margin-right: 1em; } .delete-button{ justify-content: flex-end; } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.ts b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.ts index 9c79ff6..eea2a1c 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.ts +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-genesis-editor-component/text-genesis-editor.component.ts @@ -1,71 +1,77 @@ -import {Component, Input, OnChanges, OnDestroy} from '@angular/core'; -import {CrossrefEditorDataServiceService, TextGenesis, TextVersion} from '../../crossref-editor-data-service.service'; +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {CrossrefEditorDataServiceService, TextGenesis, GuiTextVersion} from '../../crossref-editor-data-service.service'; import {Subscription} from 'rxjs'; import {ActivatedRoute} from '@angular/router'; import {NavigationServiceService} from '../../../services/navigation-service.service'; -import * as N3 from "node_modules/n3/src"; -import {QueryService} from '../../../services/query.service'; @Component({ selector: 'app-text-genesis-editor', templateUrl: './text-genesis-editor.component.html', styleUrls: ['./text-genesis-editor.component.scss'] }) -export class TextGenesisEditorComponent implements OnChanges, OnDestroy { +export class TextGenesisEditorComponent implements OnInit, OnDestroy { textGenesis: TextGenesis; textGenesisSubscription: Subscription; - n3Parser = new N3.Parser(); - n3Store = new N3.Store(); constructor(private dataService: CrossrefEditorDataServiceService, private naviService: NavigationServiceService, - private activatedRoute: ActivatedRoute, - private queryService: QueryService) { + private activatedRoute: ActivatedRoute) { if (this.activatedRoute.snapshot.queryParamMap.get('navBarOpenState') === 'false' || '') { this.naviService.updateRoute({['navBarOpenState']: 'true'}); } this.textGenesisSubscription = this.dataService.textGenesisEmitter.subscribe(tGenesis => { - console.log('textGenesis: ', tGenesis); + console.log('text Genesis: ', tGenesis); this.textGenesis = tGenesis; }) } - ngOnChanges() { + ngOnInit() { } - ngOnDestroy(){ this.textGenesisSubscription.unsubscribe(); } createTextVersion() { - let version = new TextVersion(this.queryService,'', this.textGenesis.iri, false, false, this.textGenesis.iri,); - this.textGenesis.addTextVersion(version, this.textGenesis.textVersions().length-1); - this.dataService.updateAll(this.textGenesis); + let version = GuiTextVersion.buildNew(this.textGenesis.iri()); + this.textGenesis.addTextVersion(version, this.textGenesis.textVersions().length); + this.dataService.updateAll(this.textGenesis ); } checkoutTextVersion(iri: string) { this.textGenesis.checkoutTextVersion(iri); this.dataService.updateAll(this.textGenesis); } deleteTextVersion(iri: string) { - this.dataService.textGenesis.deleteTextVersion(iri); + this.dataService.deleteGuiTextVersion(iri); + } + + undeleteTextVersion(iri: string) { + this.dataService.unDeleteGuiTextVersion(iri); } preview() { this.dataService.writeOut(false) // import somewhere } saveToTripleStore() { this.dataService.writeOut(true) } setExternity(external: boolean) { this.dataService.setExternityOfTextVersion(external); } + resetFromStore() { + if (this.textGenesis.resource.isInStore()) { this.dataService.resetFromStore(); } + } + + trackOnly(index, item) { + return item.iri() + } + } diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.html b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.html index 3c51d77..fb9598e 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.html +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.html @@ -1,13 +1,13 @@
Titel/Beschreibung - +
Link zur Resource - +
diff --git a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.ts b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.ts index 667770d..0941580 100644 --- a/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.ts +++ b/nietzsche-beta-app/src/app/crossref-editor-component/text-genesis-editor/text-version-editor-component/text-version-editor-component.ts @@ -1,39 +1,44 @@ import {Component, Input, OnChanges, OnDestroy} from '@angular/core'; -import {CrossrefEditorDataServiceService, InternalTextVersion, TextGenesis, TextVersion} from '../../crossref-editor-data-service.service'; +import { + CrossrefEditorDataServiceService, InternalTextVersion, TextGenesis, GuiTextVersion, + TlnCrossrefPage +} from '../../crossref-editor-data-service.service'; import {NavigationServiceService} from '../../../services/navigation-service.service'; import {Subscription} from 'rxjs'; import {TlnEntity} from '../../../models/models'; @Component({ selector: 'app-text-version-editor-component', templateUrl: './text-version-editor-component.html', styleUrls: ['./text-version-editor-component.scss'] }) export class TextVersionEditorComponent implements OnChanges, OnDestroy { @Input() versionIdx: number; - @Input() textVersion: TextVersion; - textGenesisSubscription: Subscription; - pages: TlnEntity[] = []; + @Input() textVersion: GuiTextVersion; + pages: TlnCrossrefPage[] = []; + extTitle: string; // the external textversions Title constructor(private naviService: NavigationServiceService, private dataService: CrossrefEditorDataServiceService) { - this.textGenesisSubscription = this.dataService.textGenesisEmitter.subscribe(tGenesis => { - if (!tGenesis.checkedOutTextVersion()) {return} - this.resetPages(tGenesis); - }); - } + } ngOnChanges() { + // updating title + this.extTitle = this.textVersion.externalTextVersion.resource._rdfDatasetToEdit.getFirstObject('http://www.nie.org/ontology/nietzsche#textUnitHasTitle') } ngOnDestroy() { - this.textGenesisSubscription.unsubscribe(); + // this.textGenesisSubscription.unsubscribe(); } - resetPages(tGenesis: TextGenesis) { + resetPagesAfterChanges(tGenesis: TextGenesis) { console.log('reset Pages with:', tGenesis); this.textVersion = tGenesis.checkedOutTextVersion(); if (tGenesis.checkedOutTextVersion().internalTextVersion._pagesCollection) { this.pages = tGenesis.checkedOutTextVersion().internalTextVersion._pagesCollection; } } + + updateExtTitle() { + this.dataService.textGenesis.checkedOutTextVersion().externalTextVersion.setTitle(this.extTitle) + } } diff --git a/nietzsche-beta-app/src/app/navigation-list-component/navigation-list-component.component.ts b/nietzsche-beta-app/src/app/navigation-list-component/navigation-list-component.component.ts index 00c704c3..fe06691 100644 --- a/nietzsche-beta-app/src/app/navigation-list-component/navigation-list-component.component.ts +++ b/nietzsche-beta-app/src/app/navigation-list-component/navigation-list-component.component.ts @@ -1,273 +1,273 @@ import {NAVTREE_DEFS} from '../constants'; import {Component, EventEmitter, OnInit} from '@angular/core'; import {NavigationServiceService} from '../services/navigation-service.service'; import {NavigationEntity, NavTreeDef, TlnEntity} from '../models/models'; import {Subscription} from 'rxjs/index'; import {ActivatedRoute, ParamMap} from '@angular/router'; import {QueryService} from '../services/query.service'; import {NavTree} from './navtree-directive.directive'; import * as _ from 'lodash'; @Component({ selector: 'app-navigation-list-component', templateUrl: './navigation-list-component.component.html', styleUrls: ['./navigation-list-component.component.scss'] }) /** * NavigationListComponent * Builds/rebuilds the navigation trees for populating the navigationlist-list-component and reacts on changes in the url */ export class NavigationListComponentComponent implements OnInit { navigationTreeDefs: NavTreeDef[]; // The definitions for buildling the navTrees navTrees: NavTree[]; // The actual Navtrees which are displayed activeTree: NavTree; // the tree which is displayed in the list component navTabIndex: number; // the index of the active tree in navTrees navTabIndexChange: EventEmitter; oldQueryParams: ParamMap; queryParamSubscription: Subscription; constructor(public naviService: NavigationServiceService, private activatedRoute: ActivatedRoute, private queryService: QueryService) { this.navigationTreeDefs = NAVTREE_DEFS; this.navTabIndexChange = new EventEmitter(); } ngOnInit() { this.oldQueryParams = this.activatedRoute.snapshot.queryParamMap; this.queryParamSubscription = this.activatedRoute.queryParamMap.subscribe((queryParams: ParamMap) => { this.reactOnNewQParams(queryParams); }); // The navTabIndex and the activeTree are set accordingly on the emitted tabIndex this.navTabIndexChange.subscribe(tabIdx => { if (tabIdx !== this.navTabIndex && tabIdx < this.navTrees.length && tabIdx >= 0) { this.navTabIndex = tabIdx; this.activeTree = this.navTrees[tabIdx]; } }); this.initNavTrees(); } // // INITIAL METHODS, CORE METHODS // async initNavTrees() { await this.createTreesOnInit(); await this.populateNavTrees(); await this.setActiveTreeOnInit(); this.subscribeToSetSelectedItems(); } createTreesOnInit() { this.navTrees = []; this.navigationTreeDefs.forEach(def => this.navTrees.push(new NavTree(def.id, def.idx, def.label, [], def.itemQParam, def.description, def.apiDef))); } /** * populateNavTrees creates the first trees if no query params are available in the url: * It ceates the manuscripNavTree and the activePageNavTreeData of the first manuscript per default. * */ async populateNavTrees(tabIdx?: number, item?: TlnEntity) { const tabStartIndex = tabIdx || 0; // where to start refreshing navtrees downward for (const treeDef of this.navigationTreeDefs.sort(def => (def.idx))) { if (treeDef.idx >= tabStartIndex) { // only create trees if needed this.queryService.getQueryfromFilename(treeDef.apiDef.query).then(async query => { let queryToRun: string; // If there is a selectedItem we have to parametrize the query if (treeDef.idx > 0) { // so we have to parametrize the query if (item) { - queryToRun = this.queryService.parametrizeQueryWithItem(query, item.id); + queryToRun = this.queryService.parametrizeWhereClauseWithItems(query, item.id); this.populateNavTree(treeDef, queryToRun); } else { // wait for selected item of the previous tab and parametrize then the query this.navTrees[treeDef.idx - 1].selectedItemSet.subscribe(selItem => { if (selItem.tabId === treeDef.idx - 1) { - queryToRun = this.queryService.parametrizeQueryWithItem(query, selItem.itemId); + queryToRun = this.queryService.parametrizeWhereClauseWithItems(query, selItem.itemId); this.populateNavTree(treeDef, queryToRun); } }); } } else { this.populateNavTree(treeDef, query); } }); } } } populateNavTree(def: NavTreeDef, query) { const idx = this.navTrees.findIndex(tree => tree.id === def.id); let parentLabel; if (idx > 0) { parentLabel = this.navTrees[idx - 1].selectedItemLabel; } if (idx !== -1) { this.queryService.getData(def.apiDef.baseUrl, query, 'SELECT').then(data => { this.navTrees[idx].setNavTreeData(_.get(data, def.apiDef.dataArray), this.activatedRoute.snapshot.queryParams, parentLabel); }); } } /** * setActiveTreeOnInit listens to the initial queryParam and sets the navTabIdx accordingly. * gets the active navTabIndex either from a passed contextView, from active qParam or sets it to 0 */ private setActiveTreeOnInit() { const activeTab = parseInt(this.activatedRoute.snapshot.queryParamMap.get('navTabIdx'), 10); this.navTrees.forEach(tree => { // If the tree is the active one according to qParam 'navTabIdx, we change to that tree if (activeTab === tree.idx) { this.navTabIndexChange.emit(activeTab); } }); } /** * subscribeToSetSelectedItems * Subscribe to each tree's selectedItemSet and route/set the selectedItem in the qParams if the item is not yet set * This applies when the trees are built initially or rebuilt, i.e. in two cases: * 1) when no qParam for the trees is set in the route * 2) a selected item is changed in a parent tree and the childs qParams are nulled */ private subscribeToSetSelectedItems() { this.navTrees.forEach(tree => { if (!this.activatedRoute.snapshot.queryParamMap.get(tree.qParam)) { tree.selectedItemSet.subscribe(item => { // set qParam this.naviService.updateRoute({[tree.qParam]: item.itemId}); }); } }); } // // REACTING ON CHANGES MADE OUTSIDE OF THE COMPONENT // async reactOnNewQParams(qParams: ParamMap) { // iterating through all queryParams and react if one param has changed await qParams.keys.forEach(key => { if (qParams.get(key) !== this.oldQueryParams.get(key)) { this.reactOnParamChange(key, qParams.get(key)); } } ); this.oldQueryParams = this.activatedRoute.snapshot.queryParamMap; } reactOnParamChange(qParam: string, newValue: string) { // qParams of the navTrees, e.g. 'page' and 'manuscript' if (this.navTrees && this.navTrees.map(tree => tree.qParam).findIndex(param => param === qParam) !== -1) { // if a qParam is one of the params defined in navTrees this.reactOnItemChange(qParam, newValue); } if (qParam === 'contextView') { this.reactOnContextChange(newValue); } if (qParam === 'navTabIdx') { this.navTabIndexChange.emit(parseInt(newValue, 10)); } } reactOnItemChange(param: string, itemId: string) { // because it might be changed from outside via url update, we have to find the tree according to the changed param const parentIdx = this.navTrees.findIndex(tree => tree.qParam === param); if (itemId !== this.navTrees[parentIdx].selectedItem) { // only rebuild if it is not yet set. this.rebuildTrees(parentIdx, itemId); } } // gets the reactOnContextChange(context: string) { const newTabIndex = this.getActiveNavTabIdxFromContext(context); this.changeNavTreeViaRoute(newTabIndex); // must time out here hence document is not ready to scroll // window.setTimeout(() => this.scrollOnToSelectedItem(this.oldQueryParams[context]), 100); } async rebuildTrees(parentIdx, itemId) { const itemToSelect = await this.navTrees[parentIdx].getSelectedById(itemId); await this.navTrees[parentIdx].setSelectedItem(itemToSelect.tlnEntity.id, itemToSelect.tlnEntity.label); await this.rebuildChildNavTrees(itemToSelect, parentIdx); } async rebuildChildNavTrees(item: NavigationEntity, parentIdx) { // get new data for subTrees and set new params accordingly await this.emptyChildTrees(parentIdx); await this.removeAllChildTreeQParams(parentIdx); await this.populateChildren(parentIdx, item.tlnEntity); } removeAllChildTreeQParams(tabId) { this.navTrees.forEach((tree, index) => { if (tree.idx > tabId) { this.navTrees[index].selectedItem = null; this.naviService.updateRoute({[tree.qParam]: null}); } }); } emptyChildTrees(parentIdx) { this.navTrees.forEach((tree, index) => { if (tree.idx > parentIdx) { this.navTrees[index].entries = []; } }); } populateChildren(parentTab, item: TlnEntity) { if (parentTab + 1 < this.navigationTreeDefs.length) { // if an item in a tab with sub tabs is selected, the subtree should be loaded according to that selection and the tab should change this.populateNavTrees(parentTab + 1, item); } } /** * getActiveNavTabIndexOnInit * gets the active navTabIndex either from a passed contextView, from active qParam or sets it to 0 */ getActiveNavTabIdxFromContext(con: string) { let navTreeIndex; // the index of the navTree with the qParam corresponding to con let navTabIndex; if (con) { navTreeIndex = this.navigationTreeDefs.findIndex(tree => tree.itemQParam === con); } if (navTreeIndex >= 0) { // no -1/not found navTabIndex = this.navigationTreeDefs[navTreeIndex].idx; } return navTabIndex; } /** * changeNavTreeViaRoute * changes to a new tree/tab in the navtab of the navList. In case of displayed tln-search and tln-crossRef * within content-view-component, the contextView is set also corresponding to the trees qParam. */ changeNavTreeViaRoute(idx: number) { if (idx < this.navTrees.length && idx >= 0) { const qParams = {}; qParams['navTabIdx'] = idx; const activeView = this.activatedRoute.snapshot.children.pop().routeConfig.path; // If the tln-search or the tln-crossRef are active, then change the contextView, hence the app reacts // on context change and will set the navTabIdx accordingly if ( activeView === 'tln-search' || activeView === 'tln-crossref' ) { const newContext = this.navTrees[this.navTrees.findIndex(tree => tree.idx === idx)].qParam; if (newContext !== this.activatedRoute.snapshot.queryParamMap.get('contextView')) { qParams['contextView'] = newContext; } } this.naviService.updateRoute(qParams); } } } diff --git a/nietzsche-beta-app/src/app/rdf-editor-module/constants.ts b/nietzsche-beta-app/src/app/rdf-editor-module/constants.ts new file mode 100644 index 0000000..a95cdec --- /dev/null +++ b/nietzsche-beta-app/src/app/rdf-editor-module/constants.ts @@ -0,0 +1,134 @@ +export const EDITINGURL: string = 'http://localhost:3030/nietzsche-rw/query'; + +// queries for parametrization + +export const RQ_ASK_EXISTS_IN_STORE = ` +ASK WHERE { + ?subjectToParametrize ?p ?o +}`; + + + +// all distinct properties whoose range is a class or a list +export const RQ_AVAILABLE_PROPERTIES = ` +PREFIX rdfs: + +select DISTINCT ?prop { + ?prop rdfs:domain ?classToParametrize . +}`; + +// all distinct properties whoose range is a class or a list +export const RQ_AVAILABLE_PROPERTIES2 = ` +PREFIX rdfs: + +select DISTINCT ?prop ?range WHERE { + ?prop rdfs:domain ?classToParametrize . + ?prop rdfs:range ?range . + {?range a . } UNION + { ?prop rdfs:range . } UNION { + ?prop rdfs:range .} UNION { + ?prop rdfs:range .} +}`; + +export const RQ_ASK_IF_CLASS_INSTANCE = ` +PREFIX rdf: +PREFIX rdfs: +PREFIX owl: + +ASK WHERE { + ?iriToReplace rdf:type ?rdfType . + ?rdfType rdf:type/rdfs:subClassOf* owl:Class . + } +`; + +export const RQ_ASK_IF_PROPERTY = ` +PREFIX rdf: +PREFIX rdfs: +PREFIX owl: + +ASK WHERE { + ?thingToReplace rdf:type/rdfs:subClassOf* owl:ObjectProperty . +}`; + +export const RQ_RDF_Type = ` +PREFIX rdf: +PREFIX rdfs: + +SELECT ?rdf_type ?typeoftype WHERE { + ?thingToParametrize rdf:type ?rdf_type . + ?rdf_type rdf:type ?typeoftype . +}`; + +export const RQ_GET_SPO = ` +PREFIX rdfs: + +select ?s ?p ?o { + ?s ?p ?o +}`; + +export const RQ_GET_DOMAIN = ` +PREFIX rdfs: +PREFIX tln: + +select ?domain WHERE { + ?predicateToExchange rdfs:domain ?domain .} +`; + +export const RQ_GET_RANGE = ` +PREFIX rdfs: +PREFIX tln: + +select ?range WHERE { + ?predicateToExchange rdfs:range ?range . } +`; + + + +export const RQ_CONSTRUCT_LISTS = ` +prefix rdf: +PREFIX tln: + +CONSTRUCT { + ?thingToReplace ?propertyToReplace ?list . + ?listRest rdf:first ?head ; + rdf:rest ?tail . +} WHERE { + ?thing tln:hasGeneticOrder ?list . + + ?list rdf:rest* ?listRest . + ?listRest rdf:first ?head ; + rdf:rest ?tail . +} +`; + +export const RQ_GET_SP_WHERE_THING_IN_LIST = ` +PREFIX rdf: +PREFIX rdfs: +PREFIX owl: + +SELECT +distinct ?s ?p ?list ?thing ?type +WHERE { + ?s ?p ?list . + ?p a owl:ObjectProperty. + ?list rdf:rest*/rdf:first ?thing . +} +`; + +// deletes all list entries of a list connected with the spo mysupject myPredicate list +export const DELETE_LIST_ENTRIES_RQ = ` +PREFIX rdf: +PREFIX tln: +DELETE + { ?anyThing rdf:first ?firstThing ; rdf:rest ?rest . } +WHERE { + ?mySubject ?myPredicate ?listToDelete . + ?listToDelete rdf:rest* ?anyThing . + ?anyThing rdf:first ?firstThing ; + rdf:rest ?rest . + } +`; + +// Deletes the triple connecting a subject to a list via myproperty +export const DELETE_SPO_RQ = ` +DELETE {?s ?p ?o } WHERE {?s ?p ?o }`; diff --git a/nietzsche-beta-app/src/app/rdf-editor-module/editor-resources.ts b/nietzsche-beta-app/src/app/rdf-editor-module/editor-resources.ts new file mode 100644 index 0000000..9b99df3 --- /dev/null +++ b/nietzsche-beta-app/src/app/rdf-editor-module/editor-resources.ts @@ -0,0 +1,588 @@ + +// The main super class for every thing which has to be added/edited or deleted in the triple store +import {StatementHandler, ChangeMgmntDef, TlnStatementCollection, OnDelete, StatementOperations} from './statement-handler'; +import {QueryService} from '../services/query.service'; +import {RdfData, RdfEditorData, RdfStoredData} from './rdf-editor-DataSets'; +import * as N3 from "node_modules/n3/src"; + +export class TlnResource { + iri: string; + label: string; + _rdfDatasetFromStore: RdfStoredData; // the data from store if there is such a thing + _rdfDatasetToEdit: RdfEditorData; // the data which is edited or created + _deprecatedVersions: DeprecatedVersion[]; // all previous depricated versions of the resource + parentIri: string; + parentLabel: string; + private _inStore: boolean; // exists in Store or in memory/JS only + private _deprecated: boolean; // states that a resource has either been edited or deleted. + private _edited: boolean; // whether an existing thing has been edited or not + private _deleted: boolean; // whether a thing has been deleted in the frontend + + + constructor(iri: string, + label?: string, + rdfsType?: string, + parentIri?: string, + parentLabel?: string) { + + this.iri = iri; + this._inStore = false; + this._deleted = false; + this._edited = false; + this._deprecated = false; + this._rdfDatasetToEdit = new RdfEditorData(this.iri, rdfsType); + this.parentIri = parentIri; + this.parentLabel = parentLabel; + } + +// loads an existing resource (or a predicate) from strore + static async buildFromStoreAsync( queryService: QueryService, + existingIri: string, + label?:string, + editorDef?: ChangeMgmntDef ){ + let resource = new TlnResource(existingIri, label); + resource._rdfDatasetFromStore = await RdfStoredData.buildAsync(queryService, existingIri, editorDef); + resource._inStore = await resource._rdfDatasetFromStore.askIfInStore(existingIri); + resource._rdfDatasetToEdit = await resource._rdfDatasetFromStore.exportAsEditorData(editorDef); + if (resource._rdfDatasetFromStore.statements() === resource._rdfDatasetToEdit.statements()) {console.log('shiiiit not be the same')} + return resource; + } + + // returns the iri's of all children depending on a given childsPredicate + async getChildIris(childsPredicate: string): Promise { + let childIris = []; + // guard if childspredicate is not available or there are no statements for the thing + if (!this._rdfDatasetToEdit.isPredicateAvailable(childsPredicate)) { return childIris } + // else we get the childIris + if (this._rdfDatasetToEdit.availablePredicates().get(childsPredicate).takesListAsObject()) { // get children out of a list as object + if (this._rdfDatasetToEdit.listsAsStatements().size === 0) { return childIris } // another guard + childIris = this._rdfDatasetToEdit.listsAsStatements().get(childsPredicate).getNodes().map(node => node.value); + } else { // get children from statements + if (this._rdfDatasetToEdit.agentStatements().size === 0) { return childIris } // another guard + childIris = this._rdfDatasetToEdit.agentStatements().get(childsPredicate).getNodes().map(node => node.value); + } + return childIris; + } + + isInStore() { + return this._inStore; + } + + isEdited() { + return this._edited; + } + + ignore() { // if the resource is not in the store and is set to deleted it must not be processed + return (!this._inStore && this._deleted); + } + + isDeleted() { + return this._deleted; + } + + rdfDatasetFromStore() { + return this._rdfDatasetFromStore; + } + + // creates a deprecated data set from the original dataset - we only deprecate existing things in store + public async deprecate(timestamp: string, def: ChangeMgmntDef): Promise { + if (!this._inStore) { return null } // guard + if (!this._deleted) { // if it is not deleted, we have to deprecate the thing against the new one + return await TlnDeprecation.buildAsync(this._rdfDatasetFromStore, timestamp, def, this._rdfDatasetToEdit) } else { + // it has been deleted, so we just depricate the whole thing without passing a new version + return await TlnDeprecation.buildAsync(this._rdfDatasetFromStore, timestamp, def) + } + } + + // Marks the TlnResource as deleted and starts deprecation of that resource with all its properties + public setDeleted() { + this._deleted = true; + } + + public setUndeleted() { + this._deleted = false; + } + + + + // returns all statements of a thing as n3.Quad[]; + // First getting all outgoing/agent statements. patientStatements are not needed, hence they are in the parent and handled by deprecation: + // if + // (properties with object) as n3 quad array in which there are both: the usual spo as well as a list containing all o's + // public getAllAgentStatementQuads(): N3.Quad[] { + // let quads: N3.Quad[] = this._rdfDatasetToEdit.statements().agentStmAsQuads(this._rdfDatasetToEdit.iri()) // getting all outgoing statements as quads + // console.log(quads); + // return quads; + // } + // + // // returns the new thing with all its poperties & objects as N3-quads/lists + // async buildRdfTransaction(): Promise { + // // get all agent statements + // let quads: N3.Quads[] = [].concat(this.getAllAgentStatementQuads()); // concadinating with [] so never undefined + // // Sometimes there is none e.g. an existing page is part of identifiesAsVersion so we don't have to create iris & properties + // // TODO get selfStatement from statementHandler - and get rid of the variable this.selfstatement + // if (this._rdfDatasetToEdit.selfStatement() !== undefined && this._rdfDatasetToEdit.selfStatement() !== null) { + // quads.splice(0,0, this._rdfDatasetToEdit.selfStatement()); + // } + // return quads; + // } + + // get all deprecated versionsof a given resource iri + async getDeprecations(queryService: QueryService, iri: string): Promise { + let deprecations: DeprecatedVersion[]; + let iris: string[] = []; // the versions iri's + // Todo: get versions by query + for (const iri of iris) { + let formerVeersion: DeprecatedVersion = await DeprecatedVersion.buildFromStoreAsync(queryService, iri); + deprecations.push(formerVeersion) + } + return deprecations; + } + + async getDeprecatedResources(resourceIri: string) { + + } + + + undeprecate(iri: string) { + // Todo: pass resource iri or verion iri? -> gui decision + let resource = this._deprecatedVersions[this._deprecatedVersions.findIndex(version => version.version.iri === iri)]; + // map resource (a tln resource) to what it is, i.e. + // populate all edcitorData with the data needed + // populate cascading! so if a Version is populated, also its textUnits must be restored/undeprecated! + // => get child from deprecatedChild??? + // if someOne saves it + // ... + } +} + + +// for undeprecating +export class DeprecatedVersion { + version: TlnResource; + resource: TlnResource; + + constructor() { + } + + static async buildFromStoreAsync(queryService, iri) { + let version = new DeprecatedVersion(); + version.version = await TlnResource.buildFromStoreAsync(queryService,iri, 'Version'); + const resourceIri = version.version._rdfDatasetFromStore.getFirstObject('http://www.nie.org/ontology/nietzsche#deprecates'); + version.resource = await TlnResource.buildFromStoreAsync(queryService,resourceIri, 'Resource'); + return version; + } +} + + +// a canvas for a resource with basic funcionality/features, i.e. only an rdf represenation of a resource with its agent statements. +// Used for not yet existing things which have to be added to the store when a deprecation takes place. +export class TlnShortHandResource { + protected _iri: string; + protected _rdfType: string; + protected _statements: N3.Quad[] = []; // all the statements of a given thing as agent, e.g. subject + + constructor() { + } + + // puts a deprecated statement + putNamedNodeStatement(predicate: string, o: string ) { + this._statements.push(RdfData.createNamedNodeQuad(this._iri, predicate, o)) + } + + // puts a deprecated statement + putLiteralStatement(predicate: string, o: string, standOffType?: string ) { + let quad = RdfData.createLiteralStatement(this._iri, predicate, o, standOffType); + this._statements.push(quad); + } + + iri() { + return this._iri; + } + + public statements() { + return this._statements; + } +} + +// The dep:classes + +export class TlnDeprecatedAgentStatement extends TlnShortHandResource{ + + constructor(predicate: string, node: N3.Literal|N3.NamedNode) { + super(); + // always put basic props + this._rdfType = 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#AgentStatement'; + this._iri = RdfData.createIri('DepAgentStm'); + this.putNamedNodeStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', this._rdfType); + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasProperty', + predicate, '^^http://www.w3.org/2001/XMLSchema#anyURI'); + // the node itsel will be written as ... + if ( node instanceof N3.Literal){ // a anyUri if the thing "was" a namednode ... + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasObject', node.value, `^^${node.datatypeString}`); + } + if (node instanceof N3.NamedNode) { // ... or it will be writte as a literal with its standofftype + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasObject', node.value, + '^^http://www.w3.org/2001/XMLSchema#anyURI'); + } + + } +} + +// the deprecated patient class: All deprecated statements/triples where the thing acted as a patient, i.e. an object +export class TlnDeprecatedPatientStatement extends TlnShortHandResource{ + + constructor(predicate: string, subject: string) { + super(); + this._rdfType = 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#PatientStatement'; + this._iri = RdfData.createIri('DepPatientStm'); + this.putNamedNodeStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', this._rdfType); + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasProperty', predicate, + '^^http://www.w3.org/2001/XMLSchema#anyURI'); + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasSubject', subject, + '^^http://www.w3.org/2001/XMLSchema#anyURI'); + } +} + +// The deprecated resource with all its properties +export class TlnDeprecatedResource extends TlnShortHandResource { // creates a new instance of dep:Deprecation to be imported + private _deprecatedStatements: any[] = []; // all statements that were valid before deprecation + protected _rdfType = 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#DeprecatedResource'; // the class of any deprecated Resource + private _depTypePredicate = 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#wasOfType'; // the predicate stating the former class + private _linkToDerivate = 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasDerivate'; // predicate to link to the derivate + + //use only builder + constructor() { + super(); + } + + // + static async buildAsync(ancestor: RdfStoredData, timeStamp: string, derivate?: RdfEditorData): Promise { + let build = new TlnDeprecatedResource(); + if (derivate !== null && derivate !== undefined) { + build.deprecateResourceWith(ancestor, derivate, timeStamp) } else { + // the resource has no derivate, so it was "deleted" completely + build.buildForDeletion(ancestor); + } + // Changing the type of the resource, but storing its old type + build.putNamedNodeStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', build._rdfType); + build.putNamedNodeStatement(build._depTypePredicate, ancestor.rdfType()); + return build; + } + + + // deprecates a resource completely without any newer state, i.e. a deletion + private buildForDeletion(ancestor: RdfStoredData) { + this._iri = ancestor.iri(); // if we delete a resource, its iri is still kept/valid and used for the deprecatedResource + // get stuff to delete: simply delete all! + + // todo: add patientstatements ... + this._deprecatedStatements = this.createDeprecatedStatements(ancestor.statements()); + } + + // deprecates a resource (ancestor) with its updated version (derivate) + private async deprecateResourceWith(ancestor: RdfStoredData, derivate: RdfEditorData, timeStamp: string) { + this._iri = `${ancestor.iri()}_deprecated_${timeStamp}`; // setting new deprecated iri + this.putNamedNodeStatement(this._linkToDerivate, derivate.iri()); // put a link to the derivate/the latest valid version + // Finally deprecating all statements which changed or got deleted + } + + + // gets all the triples which need to be created in order to deprecate, i.e. the deprecations of the resources statements + createDeprecatedStatements(ancestorStatements: StatementHandler): any[]{ + const agentStatements: TlnDeprecatedAgentStatement[] = this.createDeprecatedAgentStatements(ancestorStatements.agentStatements()); + const patientStatements: TlnDeprecatedPatientStatement[] = this.createDeprecatedPatientStatements(ancestorStatements.patientStatements()); + // Todo: get deprecatedAgentlistStatements & patientlistStatements, deletion and change mgm will be outside this method + return [... agentStatements, ... patientStatements]; + } + + createDeprecatedAgentStatements(statements: Map): TlnDeprecatedAgentStatement[] { + let depAgentStatements: TlnDeprecatedAgentStatement[] = []; + for (let [key, value] of statements) { + value.getNodes().forEach( node => { + depAgentStatements.push(new TlnDeprecatedAgentStatement(key, node)) + }); + } + return depAgentStatements; +} + + // only needed if resource gets deleted + createDeprecatedPatientStatements(statements: Map): TlnDeprecatedPatientStatement[] { + let depAgentStatements: TlnDeprecatedPatientStatement[] = []; + for (let [key, value] of statements) { + value.getNodes().forEach( node => { + depAgentStatements.push(new TlnDeprecatedPatientStatement(key, node.id)); + }) + } + return depAgentStatements; + } + +createDeprecatedAgentListAsStatements(statements: Map): TlnDeprecatedAgentStatement[] { + let depAgentStatements: TlnDeprecatedAgentStatement[] = []; + for (let [key, value] of statements) { + value.getNodes().forEach( node => { + depAgentStatements.push(new TlnDeprecatedAgentStatement(key, node)) + }); + } + return depAgentStatements; +} + + // returns all statements of the deprecated thing as an n3.Quad array, including the own statements of the dep:DeprecatedResource + // as well as the deprecatedStatements, i.e. its statements + quads(): N3.Quad[] { + let deprStatements: N3.Quad[] = []; + this._deprecatedStatements.forEach( stm => { + deprStatements.push(...stm.statements()) + }); + return [].concat(this._statements, deprStatements) + } +} + + +// contains all the triples/meta data of one transaction. +export class TlnTransaction extends TlnShortHandResource{ + _rdfType = 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#Transaction'; + committedAdditions: string; // the final ttl file + committedDeletions: string; // the final written ttl file + _additions: N3.Quad[] = []; // all the additions including additions, changes & lists to write + _deletions: N3.Quad[] = []; // all the deletions including changes + _listsToDelete: string[] = []; // all sparql queries for deleting lists + public committed = false; + + constructor() { + super(); + } + + private addTimeStampStm(ts: string) { + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasTime', + ts, 'http://www.w3.org/2001/XMLSchema#dateTimeStamp'); + } + + private addUserStm(user: string) { + if (!user || user !== '') { return } // guard + this.putLiteralStatement('https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasResponsible', user); + } + + private addCommentStm(comment: string) { + if (!comment || comment !== '') { return } // guard + this.putLiteralStatement( 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#hasComment', comment, '@de'); + } + + private addOperationType(resources: TlnResource[]) { + let oType = 'multi'; + let delCount = resources.map(res => res.isDeleted()).filter(del => del === true).length; + let edCount = resources.map(res => res.isEdited()).filter(ed => ed === true).length; + console.log('resources deleted :', delCount, ' resources edites: ', edCount, ' total resources: ', resources.length) + let inStore = resources.map(res => res.isInStore()).filter(st => st === true).length; // amount of resources in store + if (!inStore) { // nothing was in store means creation only + oType = 'creation' } + if (edCount === inStore && edCount === resources.length && delCount === 0) { // if all edited are in store & nothing has been edited + oType = 'edition' } + if (delCount === resources.length) { // all have been deleted + oType = 'deletion' } + this.putLiteralStatement( 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#transactionHasOperationType', oType, '@en'); + } + + // writes out all transaction triples as ttl + public writeOut() { + this.writeOutAdditions(); + this.writeOutDeletions(); + } + + writeOutAdditions() { + let addWriter = new N3.Writer(); + // writing statements of the Transaction + for (const statement of this._statements) { + addWriter.addQuad(statement) + } + // writing statements for the resources/deprecated resources + for (const addition of this._additions) { + addWriter.addQuad(addition) } + addWriter.end((error, result) => { + if (error) {console.error(error); return} + this.committedAdditions = result; + }) + } + + writeOutDeletions() { + let delWriter = new N3.Writer(); + for (const deletion of this._deletions) { + delWriter.addQuad(deletion) } + delWriter.end((error, result) => { + if (error) {console.error(error); return} + this.committedDeletions = result; + }) + } + + // creates the commitment from any given array of TlnResources, runs all deprecation needed + public async commit(resources: TlnResource[], user: string, comment: string, def: ChangeMgmntDef): Promise { + const ts = Date.now().valueOf().toString(); // timeStamp as string + if (def.metaData) { + // creating metadata about the transaction to perform + this._iri = RdfData.createIri('transaction'); + this.putNamedNodeStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', this._rdfType); + this.addTimeStampStm(ts); + this.addUserStm(user); + this.addCommentStm(comment); + this.addOperationType(resources); + } + + for (let resource of resources) { + if (resource.ignore() === false) { + if (resource.isInStore()) { + let deprecation = await resource.deprecate(ts, def); + this._additions.push(...deprecation.quadsToCreate()); + this._deletions.push(...deprecation.quadsToDelete()); + this._listsToDelete.push(...deprecation.listDeletionQueries()); + + } else { + // nothing to deprecate, only add new resource/statements so + // we simply add all quads directly to the transactions additions. We only handle statements top down, i.e. agentStatements only, + // hence patient statements are handled by their agents && a new resource to be created does not have any patientStatements + this._additions.push(...resource._rdfDatasetToEdit.getAllAgentStatementQuads()); + } + } + } + + console.log('additions: ', this._additions); + console.log('deletions: ', this._deletions); + if (this._additions.length + this._deletions.length === 0) { + return false } else { return true } + } + + preview() { + this.writeOut(); + } + + push(queryServeice: QueryService) { + if (!this.committed) { + console.warn('nothing to push. Commit before pushing'); + return; } + this.writeOut(); + //todo: import + } +} + + + +// creates/contains all rdf data needed if a resource gets deleted or deprecated due to changes +export class TlnDeprecation { + private _iri: string; + private deprecatedResource: TlnDeprecatedResource; // the deprecated thing dep:DeprecatedRescource with its props to be imported + private _productiveOperations: StatementOperations; // the operations needed to perform for the new valid resouce, e.g.creating, updating, deleting, ... + private _predecessor: RdfStoredData; // the original thing which needs to be depricated/deleted + private _additions: N3.Quad[] = []; // new statements of the dep:deprecation + private _depTreeDeletions: N3.Quad[] = []; // MetaData only: former statements of the deprecation tree which have to be deleted in order to update the tree + // Todo: lists??? + + constructor(predecessor: RdfStoredData) { + this._iri = RdfData.createIri('Deprecation_'); + this._predecessor = predecessor; + this._productiveOperations = new StatementOperations(); + } + + // asnc builder for the deprecation + public static async buildAsync(predecessor: RdfStoredData, timestamp: string, def: ChangeMgmntDef, derivate?: RdfEditorData): Promise { + let deprecation = new TlnDeprecation(predecessor); + if (derivate && derivate !== undefined) { // If there is a newer version of the resource we make the diff + deprecation._productiveOperations = await StatementOperations.getProductiveOperationsAsync(predecessor, + derivate, + def.updateLists); + } + if (!derivate || derivate === undefined) { // if there is no derivate at all, the resource and its statements got deleted in the gui + deprecation._productiveOperations = await deprecation.getOpsOnDelete(def.onDelete); + console.log('deprecation._productiveOperations', deprecation._productiveOperations) + } + if (def.metaData && deprecation._productiveOperations.hasChanges()) { + deprecation.addSelfStatement(); + deprecation.addOperationTypeStm(!!derivate); + // if metaData should be written and if there are any changes made, we build a deprecated resource && update the derivation tree + deprecation.deprecatedResource = await TlnDeprecatedResource.buildAsync(deprecation._predecessor, timestamp, derivate); + if (derivate && derivate !== undefined) { + deprecation.updateDerivationTree(derivate.iri()); + } + } + return deprecation; + } + + + private async getOpsOnDelete(onDelete: OnDelete): Promise { + let ops = new StatementOperations(); + if (!onDelete.keepAgentStatements) { + ops.deletions.push(... this._predecessor.statements().getAgentStmQuads(this._predecessor.iri())); + } + if (!onDelete.keepAgentLists) { + ops.setListUpdates(this._predecessor) + } + if (!onDelete.keepPatientStatements) { // deleting also all patient statements + ops.deletions.push(... this._predecessor.statements().getPatientStmAsQuads(this._predecessor.iri())); + } + + // /Todo: pATIENT Lists???! + + return ops; + } + + + // Statements for the dep:deprecation + + // adds the statement with the rdf:type of the dep:Deprecation + private addSelfStatement() { + const selfStatement = RdfData.selfStatement(this._iri, 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#Deprecation'); + this._additions.push(selfStatement); + } + + private addOperationTypeStm(edited: boolean) { + if (edited) { + this._additions.push(RdfData.createLiteralStatement(this._iri, + 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#deprecationHasOperationType', + 'edition', '@en')); } else { + this._additions.push(RdfData.createLiteralStatement(this._iri, + 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#deprecationHasOperationType', + 'deletion', '@en')); + } + } + + // Updates the derivation tree with the latest changes: Changes the link from the latest Version to this depretaion and takes over the link to the former deprecation + private updateDerivationTree(derivateIri: string) { + if (!derivateIri || derivateIri === '') { return } // guard + const formerDerivation = this._predecessor.getFirstObject('https://nietzsche.philhist.unibas.ch/ontology/deprecation#derivesFrom'); + if ( formerDerivation !== '') { + // deleting the "dep:derivesFrom" statement from the valid resource to the latest predecessor/deprecation + this._depTreeDeletions.push(RdfData.createNamedNodeQuad(derivateIri,'https://nietzsche.philhist.unibas.ch/ontology/deprecation#derivesFrom', + formerDerivation)); + // adding the "dep:derivesFrom" statement from this deprecation to the former one, i.e. its predecessor + this._additions.push(RdfData.createNamedNodeQuad(this._iri, + 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#derivesFrom', + formerDerivation)); + } + // Add the link between the new valid version/ the derivate to this deprecation which is the latest predecessor + this._additions.push(RdfData.createNamedNodeQuad(derivateIri, + 'https://nietzsche.philhist.unibas.ch/ontology/deprecation#derivesFrom', + this._iri)); + } + + // public methods/accessors + + public iri() { + return this._iri; + } + + // returns all the quads which have to be added/created as an N3.Quad array, so all the new additions as well as all + // the quads of the Dep:deprecatedResource we like to add to the store + public quadsToCreate(): N3.Quad[] { + if (this.deprecatedResource) { + return this._additions.concat(this._productiveOperations.additions, this._productiveOperations.listsToWrite, this.deprecatedResource.quads()); + } else { + return this._additions.concat(this._productiveOperations.additions, this._productiveOperations.listsToWrite) + } + } + + public quadsToDelete(): N3.Quad[] { + return [].concat(this._depTreeDeletions, this._productiveOperations.deletions); + } + + public listDeletionQueries(): string[] { + return this._productiveOperations.delListQueries; + } +} diff --git a/nietzsche-beta-app/src/app/rdf-editor-module/rdf-editor-DataSets.ts b/nietzsche-beta-app/src/app/rdf-editor-module/rdf-editor-DataSets.ts new file mode 100644 index 0000000..2c0694c --- /dev/null +++ b/nietzsche-beta-app/src/app/rdf-editor-module/rdf-editor-DataSets.ts @@ -0,0 +1,262 @@ +import { EDITINGURL } from '../constants'; +import {StatementHandler, ChangeMgmntDef, TlnPredicate, TlnStatementCollection} from './statement-handler'; +import {QueryService} from '../services/query.service'; +import * as N3 from "node_modules/n3/src"; +import {RQ_ASK_EXISTS_IN_STORE, RQ_ASK_IF_CLASS_INSTANCE, RQ_ASK_IF_PROPERTY, RQ_AVAILABLE_PROPERTIES, RQ_GET_DOMAIN, RQ_GET_RANGE, + RQ_RDF_Type} from './constants'; + +export class RdfData { + protected _iri: string; // the subjects iri + protected _rdfType: string; // rdfs type uf the subject + protected _availablePredicates: Map; // All the predicates available in the store for that resource + protected _statements: StatementHandler; + + constructor() { + } + static selfStatement(s: string, o:string): N3.Quad { + return RdfData.createNamedNodeQuad(s, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', o); + } + + // standOffType can be dataType like "http://www.w3.org/2001/XMLSchema#anyURI" or a lang tag like "@en" in the resulting literal + // [literal]^^[standoffType]. Default is a literal in german + static createLiteralStatement(s: string, p: string, value: string, standOffType = '@de'): N3.Quad { + let lang, dataType = ''; + const { DataFactory } = N3; + const { namedNode, defaultGraph, quad } = DataFactory; + let literal = new N3.Literal(`"${value}"${standOffType}`); + return quad( namedNode(s), namedNode(p), literal, defaultGraph()); + } + + static createNamedNodeQuad(s: string, p: string, o: string): N3.Quad { + const { DataFactory } = N3; + const { namedNode, quad } = DataFactory; + return quad( namedNode(s), namedNode(p), namedNode(o)) + } + + + // creates a unique iri with the given optional label, its optional parent and a generated hash built with timestamp and an additional random hash + public static createIri(label?: string, parentIri?: string) { + // unique hash: timestamp & random combined + const hash = `${Date.now().valueOf().toString(36)}-${(Math.random()).toString(36).replace('.', '')}`; + return parentIri !== undefined ? `${parentIri}${label}${hash}` : `${label}${hash}`; + } + + // public methods + + public iri() { + return this._iri; + } + + public rdfType() { + return this._rdfType; + } + + public selfStatement() { + return RdfData.createNamedNodeQuad(this.iri(), 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', this.getFirstObject('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')); + } + + public availablePredicates(): Map { + return this._availablePredicates; + } + + // returns whether a given predicate is available or not acc. to the ontology + public isPredicateAvailable(predicate: string): boolean{ + return this._availablePredicates.has(predicate); + } + + public agentStatements(): Map { + return this._statements.agentStatements(); + } + + public listStatements() { + return this._statements.agentListStatements(); + } + + public listsAsStatements() { + return this._statements.agentListsAsStatements(); + } + + public statements() { + return this._statements; + } + + // returns the first object iri for a given predicate for the resource + public getFirstObject(predicateIri: string) { + if (this.agentStatements().has(predicateIri) && this.agentStatements().get(predicateIri).getNodes().length > 0) { + return this.agentStatements().get(predicateIri).getNodes()[0].value; + } else return ''; + } + + // gets all agentstatements, i.e. also the agent list statements as quads + public getAllAgentStatementQuads(): N3.Quad[] { + let quads: N3.Quad[] = this._statements.getAllAgentStmAsQuads(this._iri); // getting all outgoing statements as quads + return quads; + } +} + + +// RdfStoredData represents the data which is stored in the triple store. It gets/contains all necessary data for any given iri +// from the triple store. All properties are immutable. +export class RdfStoredData extends RdfData{ + + constructor(private qService: QueryService, iri: string) { + super(); + this._iri = iri; + } + + // async rdfDataFactory + static async buildAsync(queryService: QueryService, iri, editorDef: ChangeMgmntDef) { + const build = await new RdfStoredData(queryService, iri); + if (!build.askIfInStore(iri)) {// guard if iri not living in store + console.warn(`Resource ${iri} not existing in store`); + return build } + build._rdfType = await build.getRdfTypeFromStore(iri); + build._availablePredicates = await build.getAvailablePredicatesFromStore(build._rdfType); + // building all statements + build._statements = await StatementHandler.buildFromStoreAsync(queryService, iri, build._availablePredicates, editorDef); + // adding "a" + let a = await TlnPredicate.buildFromStoreAsync(queryService, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', true, false ); + build._statements.putAgentStatement(a, build._rdfType); + return build; + } + + // store get methods + + // asks wether an iri is an instance of a Class + private async askIfResource(iri: string): Promise { + const query = this.qService.parametrizeWhereClauseWithItems(RQ_ASK_IF_CLASS_INSTANCE, iri); + return await this.qService.getData(EDITINGURL, query, 'ASK') + .then(isResource => (isResource['boolean'])); + } + + public async getRdfTypeFromStore(iri: string) { + const query = this.qService.parametrizeWhereClauseWithItems(RQ_RDF_Type, iri); + return await this.qService.getData(EDITINGURL, query, 'SELECT') + .then( result => result['results']['bindings'].map(p => p['rdf_type'].value)[0]) + } + + // displayableList: whiteList + private async getAvailablePredicatesFromStore(rdfType: string, displayableList?: string[], mutableList?: string[] ): Promise> { + let avPreds = new Map(); + let preds: string[]; + const query = this.qService.parametrizeWhereClauseWithItems(RQ_AVAILABLE_PROPERTIES, + '' , '', rdfType); + preds = await this.qService.getData(EDITINGURL, query, 'SELECT').then( + result => result['results']['bindings'].map(p => p['prop'].value) ); + // adding the rdfType as well as predicate + for (const p of preds) { + // todo: add disp/mutable: function displayable () + let tlnPredicate = await TlnPredicate.buildFromStoreAsync(this.qService, p); + avPreds.set(p, tlnPredicate); + } + return avPreds; + } + + // public methods + + public async askIfInStore(iri: string): Promise{ + const query = this.qService.parametrizeWhereClauseWithItems(RQ_ASK_EXISTS_IN_STORE, iri); + return await this.qService.getData(EDITINGURL, query, 'ASK') + .then(exists => (exists['boolean'])); + } + + public async exportAsEditorData(statementDef?: ChangeMgmntDef): Promise { + return await RdfEditorData.rdfEditorDataFactory(this, statementDef); // creating the rdfEditorData + } +} + +// RdfEditorData contains the rdf data to edit. To construct use either rdfEditorDataFactory() to edit an existing thing or build() +export class RdfEditorData extends RdfData { + protected _displayedPredicates: Map; // All the predicates available for display + protected _mutablePredicates: Map; // All the predicates available for editing + protected _editorDef: ChangeMgmntDef; + public constructor(iri: string, + rdfType: string) { + super(); + this._iri = iri; + this._rdfType = rdfType; + this._statements = new StatementHandler(); + } + + // async rdfEditorDataFactory, creates the RDFEditorData from a given rdfStoreData by cloning it and all properties/subproperties + static async rdfEditorDataFactory(rdfStoredData: RdfStoredData, editorDef?: ChangeMgmntDef ) { + // Todo: statementDefs: overwrite TlnPredicates? Filter them? + let rdfEditorData = new RdfEditorData(rdfStoredData.iri(), rdfStoredData.rdfType()); + rdfEditorData._editorDef = editorDef; + rdfEditorData.setAvailablePredicates(rdfStoredData.availablePredicates()); + rdfEditorData._statements = await rdfStoredData.statements().selfCloneAsync(); + return rdfEditorData; + } + + // public methods + + public setStatement(predicate: TlnPredicate, object:string, index) { + this._statements.setAgentStatement(predicate, object, index) + } + + public putStatement(predicate: TlnPredicate, object:string, index?) { + this._statements.putAgentStatement(predicate, object, index) + } + + protected setListStatement(predicate: TlnPredicate, list: string) { + this._statements.setListStatement(predicate, list); + } + + // accessor for RdfEditorData Instances + public putStatementInListAsStatement(predicate: TlnPredicate, objectIri:string, index?: number) { + this._statements.putListAsStmStatement(predicate, objectIri, index); + } + + // accessor for RdfEditorData Instances + public setStatementInListAsStatement(predicate: TlnPredicate, objectIri:string, index: number) { + this._statements.setListAsStmStatement(predicate, objectIri, index); + } + + public deleteAllAgentStatements() { + this._statements.deleteAllAgentStatements(); + } + + // accessor method RdfEditorData Instances + public deleteAllStatementsForPredicate(predicateIri: string) { + this._statements.deleteAllStatementsForPredicate(predicateIri) + } + // accessor method RdfEditorData Instances + public deleteAllStatementsWithObject(objectIri: string) { + this._statements.deleteAllObjectPresence(objectIri); + } + + // accessor method + public moveObjectInStatement(predicate: string, previousIndex: number, newIndex: number) { + this._statements.moveObjectInAgentStatement(predicate, previousIndex, newIndex); + } + + // accessor method + public moveObjectInListAsStatement(predicate: string, previousIndex: number, newIndex: number) { + console.log('moving ', predicate, previousIndex, newIndex); + this._statements.moveObjectInListAsStatement(predicate, previousIndex, newIndex); + } + + // sets or resets all _availablePredicates + public setAvailablePredicates(predicates: Map = new Map()) { + this._availablePredicates = predicates; + } + + // sets or resets all _statements + public resetListStatements(listStatements: Map = new Map()) { + this._statements.resetListStatements(listStatements); + } + + public resetListAsStatements(listAsStatements: Map) { + this._statements.resetListAsStatements(listAsStatements); + } + + public createListFromStatements() { + } + + public createListsFromStatements() { + } + + public hasStatementEntriesForPredicate(predicateIri: string) { + return this._statements.hasStatementEntriesForPredicate(predicateIri); + } +} diff --git a/nietzsche-beta-app/src/app/rdf-editor-module/statement-handler.ts b/nietzsche-beta-app/src/app/rdf-editor-module/statement-handler.ts new file mode 100644 index 0000000..341adef --- /dev/null +++ b/nietzsche-beta-app/src/app/rdf-editor-module/statement-handler.ts @@ -0,0 +1,755 @@ +import {EDITINGURL} from '../constants'; +import { + DELETE_LIST_ENTRIES_RQ, DELETE_SPO_RQ, RQ_ASK_IF_PROPERTY, RQ_CONSTRUCT_LISTS, RQ_GET_DOMAIN, RQ_GET_RANGE, + RQ_GET_SP_WHERE_THING_IN_LIST, + RQ_GET_SPO +} from './constants'; +import {QueryService, TlnQueryParametrizer} from '../services/query.service'; +import * as N3 from "node_modules/n3/src"; +import {RdfEditorData, RdfStoredData} from './rdf-editor-DataSets'; + +/** + * Class for Handling all statements + **/ +export class StatementHandler { + protected _agentStatements: Map; // all the statements of a given thing as agent, e.g. subject + protected _agentListStatements: Map; // For Resources/Subjects: Contains all connected lists + protected _agentListsAsStatements: Map; // all the lists converted to real spo + protected _patientStatements: Map; // all the statements containing the thing as patient, e.g. as object + protected _patientListStatements: Map; // all the lists containing a given thing + protected _patientListsAsStatements: Map; // all the s p to lists in which a given thing is listed + protected _editorDef: ChangeMgmntDef; + + constructor(editorDef?: ChangeMgmntDef) { + this._agentStatements = new Map(); + this._agentListStatements = new Map(); + this._agentListsAsStatements = new Map(); + this._patientStatements = new Map(); + this._patientListStatements = new Map(); + this._patientListsAsStatements = new Map(); + + if (editorDef) {this._editorDef = editorDef} else { this._editorDef = new ChangeMgmntDef()} + } + + // async builder + + // gets all statements from store for each given predicate + // listPatient: if patient === true, also all patient statements are loaded from store, i.e. all staments where the given iri acts as a object. This will take + // some time and there might even result in a server side timeout. + public static async buildFromStoreAsync(qService: QueryService, + iri: string, + availablePreds: Map, + editorDef: ChangeMgmntDef): Promise { + let build = new StatementHandler(editorDef); + for (const [key, value] of availablePreds) { + if (value.takesListAsObject()) { // if the predicate expects a list as object we set the list and also + const list = await StatementHandler.getListDataFromStore(qService, iri, key); + build.setListStatement(value, list.toString() ); + build._agentListsAsStatements = await build.convertListToIndividualStatements(value, list.toString()); + } if (!value.takesListAsObject()) { // getting objects and adding statements + const spo = await StatementHandler.getSPOFromStore(qService, iri, key); + const objects = spo.map(p => p['o'].value ); + for (const obj of objects) { + build.putAgentStatement(value, obj); } + } + } + // getting also all patient statements of the iri if patient or listPatient = true + if (build._editorDef.patientStatements) { + build._patientStatements = await StatementHandler.getPatientStatementsFromStoreAsync(qService, iri); + } + if (build._editorDef.patientListStatements) { + build._patientListsAsStatements = await build.getPatientListStatementsFromStoreAsync(qService, iri); + } + return build; + } + + // returns a Promise of a clone of the statementHandler. Iterates every property of STatementHandler and copies that to the + // the new StatementHandler stmClone which will be returned. + public async selfCloneAsync(): Promise { + let stmClone = new StatementHandler(); + for (let key of Object.keys(this)) { + if (this[key] instanceof Map) { + StatementHandler.statementCloneAsync(this[key]).then( clone => stmClone[key] = clone); + } + else { stmClone[key] = this[key] } // for editorDef + } + return stmClone; + } + + + // private methods + + + // gets the list as ttl for a given predicate + private static getListDataFromStore(qService: QueryService, subjectIri?: string, predicateIri?: string, objectIri?: string): Promise { + if (!subjectIri && !predicateIri && !objectIri) {return} // guard + let query = QueryService.parametrizeWhereClauseWithItem(RQ_CONSTRUCT_LISTS, subjectIri, predicateIri, objectIri); + return qService.getData(EDITINGURL, query, 'CONSTRUCT' ); + } + + // gets the the objects for a given subje predicate + private static async getSPOFromStore(qService: QueryService, subjectIri?: string, predicateIri?: string, objectIri?: string) { + if (!subjectIri && !predicateIri && !objectIri) {return} // guard + const query = qService.parametrizeWhereClauseWithItems(RQ_GET_SPO, subjectIri , predicateIri, objectIri ); + return await qService.getData(EDITINGURL, query, 'SELECT').then( + result => { + return result['results']['bindings']; + } + ) + } + + private static async getPatientStatementsFromStoreAsync(qService: QueryService, objectIri: string): Promise> { + let patientStatements = new Map(); // RQ_GET_SPO + const statements = await StatementHandler.getSPOFromStore(qService,'', '', objectIri); + for (const statement of statements) { + if (statement.hasOwnProperty('p') && statement.hasOwnProperty('s') && statement['s'].type !== 'bnode' ) { + if (!patientStatements.has(statement['p'].value)) {// if there is not yet a key for that property we create a new statement + patientStatements.set(statement['p'].value, new TlnStatementCollection(new TlnPredicate(statement['p'].value))) } + patientStatements.get(statement['p'].value).put(statement['s'].value); + } + } + return patientStatements; + } + + // clones a given StatementCollection Map + public static async statementCloneAsync(stms: Map) { + let stmColClone = new Map(); + if (stms.size === 0) {return stmColClone} // guard + for (let [key, value] of stms) { + if ( value instanceof TlnStatementCollection) { // then we have to deep clone it as well + stmColClone.set(key, value.selfClone()); + } + if ( value instanceof String) { // for lists: we just set the string + stmColClone.set(key, value); + } + } + return stmColClone; + } + + // gets all statements s p to lists which contain the given iri. + private async getPatientListStatementsFromStoreAsync(qService: QueryService, iri: string): Promise> { + let patientListAsStatements = new Map(); + // getting also the patient statements/incoming statements + let statements = await this.getPatientStatementsForListOccurencies(qService, iri); + for (const stm of statements) { + patientListAsStatements.set(stm.predicate().iri(), stm) } + return patientListAsStatements; + } + + // Gets all the patient statements containing to lists containing the given objectIri + // and returns the connecting outer predicate & resource + private async getPatientStatementsForListOccurencies(qService: QueryService, objectIri: string): Promise { + const query = qService.bindVariableWithIri(RQ_GET_SP_WHERE_THING_IN_LIST, 'thing', objectIri); + return await qService.getData(EDITINGURL, query, 'SELECT').then( + result => { + return result['results']['bindings'].map(entry => + new TlnStatementCollection(new TlnPredicate(entry['s'].value, new Set(['http://www.w3.org/1999/02/22-rdf-syntax-ns#List'])), + [entry['p'].value])); + } + ) + } + + // creates proper statements from a list to listAsStatements + private async convertListToIndividualStatements(predicate: TlnPredicate, list: string): Promise>{ + let listsAsStatements = new Map(); + const parser = new N3.Parser(); + await parser.parse(list, // parsing the list to quads + async (error, quad) => { // adding all predicates/objects to statements + if (quad && (quad.object.termType === 'NamedNode' || quad.object.termType === 'Literal') && + quad.object.value && + quad.object.value !== 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil') { + if (!listsAsStatements.has(predicate.iri())) {// if there is not yet a key for that property we create a new TlnStatement + listsAsStatements.set(predicate.iri(), new TlnStatementCollection(predicate)) + } + listsAsStatements.get(predicate.iri()).put(quad.object.value ); // setting the object + } + }); + return listsAsStatements; + } + + // returns all agent statements as n3 quads + public getAgentStmQuads(iri: string) { + let quads: N3.Quad[] = []; + if (this._agentStatements.size === 0) { return quads } // guard + this._agentStatements.forEach((stm) => { + stm.getNodes().forEach(object => { + if (iri && stm.n3Predicate() && object) { + quads.push(new N3.Quad(new N3.Term(iri), stm.n3Predicate(), object)); + } + }); + }); + return quads; + } + + // returns all listStatements as a new n3 list quad + public getAgentListsAsQuads(iri: string): N3.Quad[] { + let quads: N3.Quad[] = []; // guard + if (this._agentListsAsStatements.size === 0) { return quads } + this._agentListsAsStatements.forEach(stm => { + quads.push(new N3.Quad(iri, stm.n3Predicate(), new N3.Writer().list(stm.getNodes()))) // list + }); + return quads; + } + + // returns all patient statements inverted back into the original triples s p o as n3 quads + public getPatientStmAsQuads(iri: string) { + let quads: N3.Quad[] = []; + if (this._patientStatements.size === 0) { return quads } // guard + this._patientStatements.forEach((stm) => { + stm.getNodes().forEach(object => { + if (iri && stm.n3Predicate() && object) { + // inverting object with subject, hence in patient statements the iri is the actual object in the statements + quads.push(object, stm.n3Predicate(), new N3.Quad(new N3.Term(iri) )); + } + }); + }); + return quads; + } + + // public methods + + // puts as object to a + public putAgentStatement(predicate: TlnPredicate, objectIri: string, index?: number) { + if (!this._agentStatements.has(predicate.iri())) {// if there is not yet a key for that property we create a new statement + this._agentStatements.set(predicate.iri(), new TlnStatementCollection(predicate)) } + this._agentStatements.get(predicate.iri()).put(objectIri, index); + } + + // + public setAgentStatement(predicate: TlnPredicate, objectIri: string, index: number) { + if (!this._agentStatements.has(predicate.iri())) {// if there is not yet a key for that property we create a new statement + this._agentStatements.set(predicate.iri(), new TlnStatementCollection(predicate))} + this._agentStatements.get(predicate.iri()).set(objectIri, index); + } + + // As there should be only one list as object per subject and predicate - everything else is a data mess - we simply set it + public setListStatement(predicate: TlnPredicate, list: string) { + if (!this._agentListsAsStatements.has(predicate.iri())) {// if there is not yet a key for that property we create a new statement + this._agentListsAsStatements.set(predicate.iri(), new TlnStatementCollection(predicate)) } + this._agentListStatements.set(predicate.iri(), list); + } + + // puts as object to a + public putListAsStmStatement(predicate: TlnPredicate, objectIri: string, index?: number) { + if (!this._agentListsAsStatements.has(predicate.iri())) {// if there is not yet a key for that property we create a new statement + this._agentListsAsStatements.set(predicate.iri(), new TlnStatementCollection(predicate)) } + this._agentListsAsStatements.get(predicate.iri()).put(objectIri, index); + } + + // sets a statement in _listAsStatements + public setListAsStmStatement(predicate: TlnPredicate, objectIri: string, index) { + this._agentListsAsStatements.get(predicate.iri()).set(objectIri, index); // setting the object + } + + + public deleteAllStatementsForPredicate(predicateIri: string) { + if (this.hasStatementForPredicate(predicateIri) && this.hasStatementEntriesForPredicate(predicateIri)) { + this._agentStatements.delete(predicateIri); + } + } + + public deleteAllObjectPresence(objectIri: string) { + this.deleteAgentStatementsWithO(objectIri); + this.deleteAllListAsStatementsWithObject(objectIri); + } + + //deletes all agentStatements as well as the agentListAsStatements + public deleteAllAgentStatements() { + this._agentStatements.forEach((value, key) => { + this._agentStatements.get(key).deleteAll(); + }); + this._agentListsAsStatements.forEach((value, key) => { + this._agentListsAsStatements.get(key).deleteAll(); + }); + } + + // deletes all _agentStatements containing a given object + public deleteAgentStatementsWithO(objectIri: string) { + this._agentStatements.forEach(statement => { + const idx = statement.nodeIdx(objectIri); + if (idx > -1) { + statement.del(idx); + } + }); + } + + // deletes all listasaStaments + public deleteAllListAsStatementsWithObject(objectIri: string) { + console.log('HHHHHHHHHHHHHHHHHH', objectIri) + this._agentListsAsStatements.forEach((value, key) => { + console.log(value); + let idx = value.nodeIdx(objectIri); + if (idx !== -1) { + value.del(idx); + console.log('deleting ', objectIri) + } else {console.warn(`Resource ${objectIri} not found.`)} + }); + } + + // Obviously not needed in RDF itself, but useful for gui xperience and testing + public moveObjectInAgentStatement(predicate: string, previousIndex, newIndex) { + this._agentStatements.get(predicate).move(previousIndex, newIndex); + } + + public moveObjectInListAsStatement(predicate: string, previousIndex, newIndex) { + this._agentListsAsStatements.get(predicate).move(previousIndex, newIndex); + } + + // sets all _statements to a given statementCollection or empties all + public resetAgentStatements(statements: Map = new Map()) { + this._agentStatements= new Map(statements); + } + + // sets or resets all _statements + public resetListStatements(listStatements: Map = new Map()) { + this._agentListStatements = new Map(listStatements); + } + + public resetListAsStatements(listAsStatements: Map) { + this._agentListsAsStatements = new Map(listAsStatements); + } + + public resetPatientStatements(patientStatements: Map) { + this._patientStatements = new Map(patientStatements); + } + + public resetPatientListAsStatements(patientListAsStatements: Map) { + this._patientListsAsStatements= new Map(patientListAsStatements); + } + + public resetPatientListStatements(patientListStatements: Map) { + this._patientListStatements = new Map(patientListStatements); + } + + // returns whether a thing has a statements built with a certain property or not + public hasStatementForPredicate(predicateIri: string): boolean { + return this._agentStatements.get(predicateIri) !== undefined && this._agentStatements.get(predicateIri) !== null; + } + + // Todo: remove + // returns all statementsAsList objects + public async getListObjectsForPredicate(predicate: string) { + if (this._agentListsAsStatements.size) { + let fuu = this._agentListsAsStatements.get(predicate) + } + let fuu = this._agentListsAsStatements.get(predicate) + return fuu; + } + + // returns whether there is a statement conecting an object + public hasStatementWithObject(objectIri: string) { + let hasStatement = false; + this._agentStatements.forEach(statement => { + if (statement.hasNode(objectIri)) { hasStatement = true} + }); + return hasStatement + } + + + public hasStatementEntriesForPredicate(predicateIri) { + return this.hasStatementForPredicate(predicateIri) && this._agentStatements.get(predicateIri).getNodes().length > 0; + } + + public agentStatements() { + return this._agentStatements; + } + + public agentListStatements() { + return this._agentListStatements; + } + + public agentListsAsStatements() { + return this._agentListsAsStatements; + } + + public patientStatements() { + return this._patientStatements; + } + + public patientListStatements() { + return this._patientListStatements; + } + + public patientListAsStatements() { + return this._patientListsAsStatements; + } + + // returns all agent statements & agentLists statements of a thing as n3.Quad[]; + public getAllAgentStmAsQuads(iri: string): N3.Quad[] { + let quads: N3.Quad[]; + quads = [... this.getAgentStmQuads(iri), ... this.getAgentListsAsQuads(iri)]; + return quads; + } + + // returns all patient statements inverted as agent statements as n3.Quad[]; + public getAllPatientStmAsAgentQuads(iri: string): N3.Quad[] { + let quads: N3.Quad[]; + //quads = [... this.getAgentStmQuads(iri), ... this.getAgentListsAsQuads(iri)]; + return quads; + } + + // returns all patient statements & patient listAsstatements of a thing as n3.Quad[]; + public getAllPatientStmAsQuads(iri: string): N3.Quad[] { + let quads: N3.Quad[]; + // quads = [... this.getAgentStmQuads(iri), ... this.getAgentListsAsQuads(iri)]; + return quads; + } +} + +export class TlnStatementCollection { + private _predicate: TlnPredicate; + protected _n3PredicateNode: N3.NamedNode; + protected _nodes: N3.NamedNode|N3.Literal[]; + + constructor(predicate: TlnPredicate, nNodes: string[] = []) { + this._n3PredicateNode = new N3.NamedNode(predicate.iri()); + this._predicate = predicate; + this._nodes = nNodes.map(val => this.createNode(val) ); + } + + private createNode(value: string) { + const { DataFactory } = N3; + if (this._predicate.takesLiteralAsObject()) { + const { literal } = DataFactory; + return literal(value) } else { + const { namedNode } = DataFactory; + namedNode.id = `${Date.now().valueOf().toString(36)}-${(Math.random()).toString(36).replace('.', '')}`; + return namedNode(value) } + } + + // returns a cloned version of the TlnStatementCollection + public selfClone() { + let stmCol = new TlnStatementCollection(this.predicate()); + stmCol._nodes = [...this._nodes]; + return stmCol; + } + + // puts a given object to a given index or adds it to the end if no index passed + // if no index the object will be added at the end as default + public put(value: string, index = this.getNodes().length) { + if (this._nodes.findIndex(obj => obj.id === value) !== -1) { + console.warn('Node already in statement', value); + } + this._nodes.splice(index, 0, this.createNode(value)); + } + + // overwrites an object at a given index + public set(value: string, index: number) { + this._nodes.splice(index, 1, this.createNode(value)); + } + + // deletes an object/one node + public del(index) { + this._nodes.splice(index, 1); + } + + // deletes all nodes/objects + public deleteAll() { + this._nodes = []; + } + + // moves a node in the _nodes array + public move(previousIndex, newIndex) { + const node = this._nodes[previousIndex]; + this._nodes.splice(previousIndex, 1); + this._nodes.splice(newIndex, 0, node); + } + + public setNodes(values: string[]) { + this._nodes = values.map(iri => this.createNode(iri)); + } + + public predicate() { + return this._predicate; + } + + public n3Predicate() { + return this._n3PredicateNode; + } + + public getNodes(): N3.NamedNode|N3.Literal[] { + return this._nodes; + } + + // returns whether a TlnStatements instance contains a node with a given iri + public hasNode(iri: string) { + return this._nodes.findIndex(node => node.id === iri)>-1; + } + + // returns the index of the object in this._nodese + public nodeIdx(objectIri: string): number { + return this._nodes.findIndex(node => node.id === objectIri); + } +} + + +// Operations to perform on the valid, productive dataset. No dep/deprecation-namespace here +export class StatementOperations { + additions: N3.Quad[] = []; + changes: N3.Quad[] = []; + deletions: N3.Quad[] = []; + delListQueries: string[] = []; // the sparql queries to delete listEntries & to delete the "s p list" triples + listsToWrite: N3.Quad[] = []; // the updated lists as quads to be imported + + constructor() { + } + + // builds the operation to perform by comparing two Maps of statement collections. + public static async getProductiveOperationsAsync(predecessor: RdfStoredData, + derivate: RdfEditorData, + updateLists: boolean): Promise { + let ops: StatementOperations = new StatementOperations(); + predecessor.agentStatements().forEach(async (value, key) => { + // for every stm we check if it has been deleted or changed + if (derivate.agentStatements().has(key)) { // if the derivate statements contain at least one statement with the given predicate + // we check which ones have been deleted or changed ... + let difPerPredicate = await ops.compare(predecessor.iri(), key, value.getNodes(), derivate.agentStatements().get(key).getNodes()); + // and push them + difPerPredicate.additions.forEach(a => ops.additions.push(a)); + difPerPredicate.changes.forEach(c => ops.changes.push(c)); + difPerPredicate.deletions.forEach(d => ops.deletions.push(d)); + } else { // they have all been deleted + for (let node of value.getNodes()) { + const quad = new N3.Quad(new N3.Term(predecessor.iri()), new N3.Term(key), node); + ops.deletions.push(quad); + } + } + // checking also if there are completely new statements + derivate.agentStatements().forEach(async (value, key) => { + if (!predecessor.agentStatements().has(key)) { // then the predicate is completely new + value.getNodes().forEach(node => { + const addedQuad = new N3.Quad(new N3.Term(derivate.iri()), new N3.Term(key), node); + ops.additions.push(addedQuad); + }); + } + }) + }); + + // processing listStatements + if (updateLists) { + if (derivate.iri() === 'http://rdfh.ch/projects/0068#_Mp_XIV_IdentifiedTextVersion0') {console.log('LISTS UPDATING', predecessor, derivate)} + ops.setListUpdates(predecessor, derivate); + } + + return ops; + } + + setListUpdates(ancestor: RdfStoredData, derivate?: RdfEditorData) { + ancestor.listsAsStatements().forEach(async (value, key) => { + let delListEntriesQuery: string; + let delSPOToList: string; + // for every stm we check if it has been deleted or changed + if (!derivate) { + [delListEntriesQuery, delSPOToList] = TlnQueryParametrizer.getDelQueriesOfListsOfSP(ancestor.iri(),key); + } + if (derivate && derivate.listsAsStatements().has(key) ) { + // if the derivate statements have statements with the given predicate/key + if (this.listHasChanges(ancestor.iri(), key, value.getNodes(), derivate.listsAsStatements().get(key).getNodes())) { + // and if there have been made changes within the list + if (derivate.iri() === 'http://rdfh.ch/projects/0068#_Mp_XIV_IdentifiedTextVersion0') {console.log('hasChanges');} + // getting queries to delete lists and their entries + [delListEntriesQuery, delSPOToList] = TlnQueryParametrizer.getDelQueriesOfListsOfSP(derivate.iri(),key); + // set the new lists to write + let listToWrite = derivate.statements().getAgentListsAsQuads(derivate.iri()); + console.log('listToWrite', listToWrite); + this.listsToWrite = [this.listsToWrite, ...derivate.statements().getAgentListsAsQuads(derivate.iri())]; + } + } + if (delListEntriesQuery && delListEntriesQuery!== '') { this.delListQueries.push(delListEntriesQuery) } + if (delSPOToList && delSPOToList!== '') { this.delListQueries.push(delSPOToList) } + }); + } + + // returns whether there are any changes or not + public hasChanges() { + return (this.deletions.length + this.additions.length + this.changes.length + this.delListQueries.length + this.listsToWrite.length > 0 ); + } + + // compares all statements for one given predicate and returns a dif for the passed nodes + public async compare(subject: string, predicateIri: string, ancestorNodes: N3.NamedNode|N3.Literal[], derivateNodes: N3.NamedNode|N3.Literal[]): Promise {; + let ops: StatementOperations = new StatementOperations(); + // first check if every ancestorNode has changed or been deleted + for ( let i = 0; i < ancestorNodes.length; i++ ) { // checking every stm + let derIdx = derivateNodes.findIndex(_node => _node.id === ancestorNodes[i].id); + if (derIdx === -1) { + // if the id is not in derivateNodes it has been deleted + const delQuad = new N3.Quad(new N3.Term(subject), new N3.Term(predicateIri), ancestorNodes[i]); + ops.deletions.push(delQuad) } else { + // we check if it has been moved in the array + if (derIdx !== i) { // it has been moved + + } + + // it is in the array, so we check for possible changes + if (JSON.stringify(derivateNodes[derIdx]) !== JSON.stringify(ancestorNodes[i])) { // it has been changed + const changedQuad = new N3.Quad(new N3.Term(subject), new N3.Term(predicateIri), derivateNodes[derIdx]); + ops.changes.push(changedQuad); + } + } + } + // then checking additions backwards + for (let node of derivateNodes) { + if (node.id === 'http://rdfh.ch/projects/0068#_Mp_XIV_Page419a') { + console.log('node', node); + console.log('ancestorNodes', ancestorNodes); + } + + if (ancestorNodes.findIndex(n => n.id === node.id) === -1) { + const addedQuad = new N3.Quad(new N3.Term(subject), new N3.Term(predicateIri), node); + ops.additions.push(addedQuad); + console.log('addedQuad', addedQuad) + } + } + + return ops; + } + + // compares all statements for one given predicate and returns a dif for the passed nodes + public listHasChanges(subject: string, predicateIri: string, ancestorNodes: N3.NamedNode|N3.Literal[], derivateNodes: N3.NamedNode|N3.Literal[]): boolean { + // If length differs; covers + let changed = false; + if (ancestorNodes.length !== derivateNodes.length) {changed = true} + + // first check if every ancestorNode has been exchanged, deleted or moved in the derivate array; covers also deletion of x addition of y + for ( let i = 0; i < ancestorNodes.length; i++ ) { // checking every stm + let derIdx = derivateNodes.findIndex(_node => _node.id === ancestorNodes[i].id); + if (derIdx === -1 || derIdx !== i) { // has been deleted, exchanged or moved + changed = true; + break; } + } + return changed; + } +} + +export class TlnPredicate { + private _iri: string; + private _ranges: Set; + private _domains: Set; + private _label: string; // if no label passed generates a label from the iri per default + private _displayable = true; + private _mutable = true; + + constructor(iri: string, + ranges = new Set(), + domains = new Set(), + label?, // if no label passed generates a label from the iri per default + displayable = true, + mutable = true ) { + this._iri = iri; + this._ranges = ranges; + this._domains = domains; + if (!label || label === '') { this._label = iri.split('#')[1] } + this._displayable = displayable; + this._mutable = mutable; + } + + // static functions + + // async builder + public static async buildFromStoreAsync(queryService: QueryService, predicateIri: string, displayable = true, mutable = true) { + let ranges = await TlnPredicate.getRangesFromStore(queryService, predicateIri); + let domains = await TlnPredicate.getDomainsFromStore(queryService, predicateIri); + // Todo: get label from store ... + return new TlnPredicate(predicateIri, new Set(ranges), new Set(domains), '' , displayable, mutable); + } + + public static async askIfPredicate(qService: QueryService, iri: string): Promise { + const query = qService.parametrizeWhereClauseWithItems(RQ_ASK_IF_PROPERTY, iri); + return await qService.getData(EDITINGURL, query, 'ASK') + .then(isPredicate => (isPredicate['boolean'])); + } + + private static async getDomainsFromStore(qService: QueryService, owlPropertyIri: string): Promise { + const query = qService.parametrizeWhereClauseWithItems(RQ_GET_DOMAIN, owlPropertyIri); + return await qService.getData(EDITINGURL, query, 'SELECT') + .then(result => result['results']['bindings'].map(p => p['domain'].value)) + } + + private static async getRangesFromStore(qService: QueryService, owlPropertyIri: string): Promise { + const query = qService.parametrizeWhereClauseWithItems(RQ_GET_RANGE, owlPropertyIri); + return await qService.getData(EDITINGURL, query, 'SELECT') + .then(result => result['results']['bindings'].map(p => p['range'].value)) + } + + private putRange(range: string) { + if (this._ranges.values()) + this._ranges.add(range); + } + + // accessors + + public iri() { + return this._iri + } + + public label() { + return this._label; + } + + public ranges() { + return this._ranges + } + + public domains() { + return this._domains + } + + public displayable() { + return this._displayable + } + + public mutable(): boolean { + return this._mutable + } + + // returns whether a the range is a list or not + public takesListAsObject(): boolean { + return this._ranges.has('http://www.w3.org/1999/02/22-rdf-syntax-ns#List'); + } + + // returns whether a the range is a Literal + public takesLiteralAsObject(): boolean { + return this._ranges.has('http://www.w3.org/2001/XMLSchema#string'); + } + + // returns whether a the range is a Literal + public takesUrlAsObject(): boolean { + return this._ranges.has('http://www.w3.org/2001/XMLSchema#anyURI'); + } +} + +// setting parameters for a StatementHandler: The recipe on how to build it +// param: agentStatement: get agent statemreents from store +// param: agentListStatements ... +// whiteListedPredicates: only these properties will be loaded from store by the statementHandler +export class ChangeMgmntDef { + + constructor( + public agentStatements = true, + public agentListStatements = true, + public patientStatements = false, + public patientListStatements = false, + public updateLists = true, + public displayedPredicates: string[] = [], // whiteList + public mutablePredicates: string[] = [], // whiteList + public hiddenPredicates: string[] = [], + public immutablePredicates: string[] = [], + public metaData = true, // whether metadata is written on change or not + onDelete?: OnDelete // what should be deleted/kept on deleteing a resource + + ) { + if (onDelete && onDelete !== undefined) { this.onDelete = onDelete } else { + this.onDelete = {keepAgentStatements: false, keepAgentLists: false, keepPatientStatements: false, keepPatientLists: false} + } + } + onDelete: OnDelete; + +} + +export interface OnDelete { + keepAgentStatements: boolean; + keepAgentLists: boolean; + keepPatientStatements: boolean; + keepPatientLists: boolean; +} + diff --git a/nietzsche-beta-app/src/app/services/query.service.ts b/nietzsche-beta-app/src/app/services/query.service.ts index 237d73d..fe91ff4 100644 --- a/nietzsche-beta-app/src/app/services/query.service.ts +++ b/nietzsche-beta-app/src/app/services/query.service.ts @@ -1,115 +1,201 @@ import { Injectable } from '@angular/core'; import { Parser, Generator, Wildcard } from 'sparqljs'; import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {DELETE_LIST_ENTRIES_RQ, DELETE_SPO_RQ} from '../rdf-editor-module/constants'; @Injectable() export class QueryService { constructor(private http: HttpClient) { } /** * Gets the data from an endpoint via http post * * @param baseUrl: The url of the endpoint. * @param query: The query to run. * @param queryType: "CONSTRUCT" or "QUERY" * @returns the response. */ public getData(baseUrl: string, query: string, queryType?: string ) { let httpOptions; if (queryType === 'CONSTRUCT') { // A construct does contain a text as response, not a json, so responseType must be 'text' to avoid parse errors httpOptions = { headers: new HttpHeaders({'Content-Type': 'application/sparql-query', 'Accept': 'text/turtle'}), responseType: 'text'}; return this.http.post(baseUrl, query, httpOptions).toPromise(); } if (queryType === 'SELECT' || queryType === 'ASK') { httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/sparql-query', 'Accept': 'application/sparql-results+json; charset=UTF-8'})}; return this.http.post(baseUrl, query, httpOptions).toPromise(); } } + public updateData(baseUrl: string, data: string) { let query = `INSERT DATA {${data}}`; - console.log('query', query); let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/sparql-update', 'Authorization': 'Basic bmlldHpzY2hlX3VzZXI6bmlldHpzY2hl', 'Accept': 'application/sparql-results+json; charset=UTF-8'}), method: 'POST', body: query}; this.http.post(baseUrl, query, httpOptions).toPromise().then(fuu => console.log(fuu) ); } /** * Gets a text file by its name from the directory assets/queries. * * @param filename The name of the file + file name extension. * @returns the text of the file. */ public getQueryfromFilename(filename) { return this.http.get('../assets/queries/' + filename, {responseType: 'text'}).toPromise(); } /** - * getQueryForItem() - * Parametrizes a given baseQuery with a iri passed, so the iri will be the resource of the where clause + * parametrizeSimpleQueryWithItem() + * Parametrizes a given baseQuery's where clause with an iri passed. only the first where clause will be parametrized * * @param subjectIri: The iri of the selected resource * @param baseQuery: name of the query to parametrize * @param propertyIri: The iri of the selected property * @param objectIri: The iri of the selected object * @returns the query for the resource. */ - public parametrizeQueryWithItem(baseQuery: string, subjectIri?: string, propertyIri?: string, objectIri?: string): string { + public parametrizeWhereClauseWithItems(baseQuery: string, subjectIri?: string, predicateIri?: string, objectIri?: string): string { + let qParametrizer = new TlnQueryParametrizer(baseQuery); + qParametrizer.parametrizeWhereSPO(subjectIri, predicateIri, objectIri); + return qParametrizer.writeOut(); + } + + // the static version + public static parametrizeWhereClauseWithItem(baseQuery: string, subjectIri?: string, propertyIri?: string, objectIri?: string): string { + let qParametrizer = new TlnQueryParametrizer(baseQuery); + qParametrizer.parametrizeWhereSPO(subjectIri, propertyIri, objectIri); + return qParametrizer.writeOut(); + } + + // binds an iri to a variable and adds the BIND on top of the Where clause + public bindVariableWithIri(baseQuery: string, variable: string, iri: string) { + let qParametrizer = new TlnQueryParametrizer(baseQuery); + qParametrizer.bindVariable(variable, iri); + return qParametrizer.writeOut(); + } + + // binds an iri to a variable and adds the BIND on top of the Where clause + public static bindVariableWithIri(baseQuery: string, variable: string, iri: string, type = 0) { let qParametrizer = new TlnQueryParametrizer(baseQuery); - qParametrizer.parametrize(subjectIri, propertyIri, objectIri); + qParametrizer.bindVariable(variable, iri, type); return qParametrizer.writeOut(); } } export class TlnQueryParametrizer { parsedQuery; parser: Parser; sparqlGenerator = new Generator({}); constructor(baseQuery: string) { this.parser = new Parser(); this.parsedQuery = this.parser.parse(baseQuery); - console.log(this.parsedQuery); } - parametrize(subjectIri?: string, predicateIri?: string, objectIri?: string) { - this.setSubject(subjectIri); - this.setPredicate(predicateIri); - this.setObject(objectIri); + // parametrizes the first occurence of a given s p o in the first where clause + parametrizeWhereSPO(subjectIri?: string, predicateIri?: string, objectIri?: string) { + // get the idx of the first where clause + this.setNode(subjectIri, 'subject'); + this.setNode(predicateIri, 'predicate'); + this.setNode(objectIri, 'object'); + } + + setNode(iri: string, role: string): void { + if (!iri || iri === '') { return } // guard + if (this.parsedQuery.type === 'update') { + this.parsedQuery.updates[0].where[0].triples[0][role] = {'termType': 'NamedNode', 'value': decodeURI(iri) }; + } else { + const whereIdx = this.parsedQuery.where.findIndex(where => where.type === 'bgp'); + // console.log(this.parsedQuery.where[whereIdx].triples[0][role]) + this.parsedQuery.where[whereIdx].triples[0][role] = {'termType': 'NamedNode', 'value': decodeURI(iri) }; + if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the template subject + this.parsedQuery.template[0][role] = {'termType': 'NamedNode', 'value': decodeURI(iri) }; + } + } } - setSubject(subjectIri: string): void { - if (!subjectIri || subjectIri === '') { return } - this.parsedQuery.where[0].triples[0].subject = {'termType': 'NamedNode', 'value': decodeURI(subjectIri) }; - if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the property + setSubject(subjectIri: string, whereIdx): void { + if (!subjectIri || subjectIri === '') { return } // guard + this.parsedQuery.where[whereIdx].triples[0].subject = {'termType': 'NamedNode', 'value': decodeURI(subjectIri) }; + if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the template subject this.parsedQuery.template[0].subject = {'termType': 'NamedNode', 'value': decodeURI(subjectIri) }; } } - setPredicate(predicateIri: string): void { + setPredicate(predicateIri: string, whereIdx): void { //guard if (!predicateIri || predicateIri === '') { return } - this.parsedQuery.where[0].triples[0].predicate = {'termType': 'NamedNode', 'value': decodeURI(predicateIri) }; - if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the property + this.parsedQuery.where[whereIdx].triples[0].predicate = {'termType': 'NamedNode', 'value': decodeURI(predicateIri) }; + if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the template perdicate this.parsedQuery.template[0].predicate = {'termType': 'NamedNode', 'value': decodeURI(predicateIri) }; } } - setObject(objectIri: string): void { - if (!objectIri || objectIri === '') { return } - this.parsedQuery.where[0].triples[0].object = {'termType': 'NamedNode', 'value': decodeURI(objectIri) }; - if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the property + setObject(objectIri: string, whereIdx): void { + if (!objectIri || objectIri === '') { return } // guard + this.parsedQuery.where[whereIdx].triples[0].object = {'termType': 'NamedNode', 'value': decodeURI(objectIri) }; + if (this.parsedQuery.queryType === 'CONSTRUCT') { // in case of constructs we also change the template object this.parsedQuery.template[0].object = {'termType': 'NamedNode', 'value': decodeURI(objectIri) }; } } + + bindVariable(variable: string, iri: string, type: number = 0) { + let bind = { + expression: { + termType: "Uri", + value: iri, + language: "", + datatype: {termType: "NamedNode", value:"http://www.w3.org/2001/XMLSchema#string" } + }, + type: "bind", + variable: {termType: 'Variable', value: variable} } + if (type === 0) { // select query + this.parsedQuery.where.splice(0, 0, bind ); + } + if (type === 1) { //updatedelete + // console.log(type, variable, iri); + this.parsedQuery.updates[0].where.splice(0, 0, bind ); + } + } + + // sets all occurencies of a variable + setVars(node: string, value: string) { + let bgp = this.parsedQuery.where.filter(where => where.type === 'bgp'); + if (bgp.length <= 1 || !bgp.triples || bgp.triples.length <= 1) { return} // guard + const valueNode = {'termType': 'NamedNode', 'value': decodeURI(value)}; + let variableToParametrize = bgp.triples[0][node].value; + // replacing all occurenceis of variableToParametrize in the where clause + for (let i = 0; i <= this.parsedQuery.where.length; i++) { + if (this.parsedQuery.where[i].triples[0].subject.value === variableToParametrize) { + this.parsedQuery.where[i].triples[0].subject = valueNode; } + if (this.parsedQuery.where[i].triples[0].predicate.value === variableToParametrize) { + this.parsedQuery.where[i].triples[0].predicate = valueNode } + if (this.parsedQuery.where[i].triples[0].object.value === variableToParametrize) { + this.parsedQuery.where[i].triples[0].object = valueNode } + } + } + + // Deletes all list entries & all lists of a subject for a given predicate + public static getDelQueriesOfListsOfSP(subjectIri: string, predicateIri: string): string[] { + // parametrize first the subject + const qSubject = QueryService.bindVariableWithIri(DELETE_LIST_ENTRIES_RQ, 'mySubject', subjectIri, 1); + // then the predicate + const delListEntriesQuery = QueryService.bindVariableWithIri(qSubject, 'myPredicate', predicateIri, 1); + console.log('delListEntriesQuery', delListEntriesQuery) + // parametrize the query to delete the triples + const delSPOToList = QueryService.parametrizeWhereClauseWithItem(DELETE_SPO_RQ, subjectIri, predicateIri) ; + return [delListEntriesQuery, delSPOToList]; + } + writeOut() { return this.sparqlGenerator.stringify(this.parsedQuery); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/constants.ts b/nietzsche-beta-app/src/app/tln-edition/constants.ts index 4f092b1..b0c98fb 100644 --- a/nietzsche-beta-app/src/app/tln-edition/constants.ts +++ b/nietzsche-beta-app/src/app/tln-edition/constants.ts @@ -1,83 +1,87 @@ export {HIGHTLIGHT_CASES} from '../page-view/highlight_status'; export enum VIEW_OPTIONS { TRANSKRIPTION = 'Transkription', FAKSIMILE = 'Faksimile', SYNOPSIS = 'Transkription/Faksimile', SYNOPSIS_B = 'Faksimile/Transkription' } export const DEFAULT_VIEW_OPTION: string = VIEW_OPTIONS.SYNOPSIS_B; export const ONTOLOTY_PREFIX: string = 'http://www.nie.org/ontology/nietzsche#' /** * Route for TlnCrossrefComponent **/ export const TLN_CROSSREF_ROUTE: string = 'tln-crossref'; /** * Route for TlnCrossrefComponent **/ export const TLN_CROSSREF_EDITOR_ROUTE: string = 'tln-crossref-editor'; +/** + * Route for TlnResourceEditorComponent + **/ +export const TLN_RESSOURCE_EDITOR_ROUTE: string = 'tln-rsource-editor'; /** * Route for TlnFulltextComponent **/ export const TLN_SEARCH_ROUTE: string = 'tln-search'; /** * Route for TlnManuscriptViewComponent **/ export const TLN_MANUSCRIPT_ROUTE: string = 'tln-manuscript'; /** * Route for TlnViewerComponent **/ export const TLN_VIEWER_ROUTE: string = 'tln-viewer'; /** * Param that refers to the context that should be shown, i.e. 'page' or 'manuscript'. **/ export const TLN_CONTEXT_VIEW_PARAM: string = 'contextView'; /** * Param that toggles fullscreen, value type: boolean. **/ export const TLN_FULLSCREEN_PARAM: string = 'fullscreen'; /** * Param for find text in page. **/ export const TLN_FIND_PARAM: string = 'find'; /** * Param for manuscript iri. **/ export const TLN_MANUSCRIPT_PARAM: string = 'manuscript'; /** * Param for navigation bar open state. **/ export const TLN_NAV_BAR_OPEN_STATE_PARAM: string = 'navBarOpenState'; /** * Param for page iri. **/ export const TLN_PAGE_PARAM: string = 'page'; /** * Param for result index, type: number. **/ export const TLN_RESULT_INDEX_PARAM: string = 'resutlIndex'; /** * Param for selected lines. **/ export const TLN_SELECTED_LINES_PARAM: string = 'selectedLines'; /** * Param for selected lines. **/ export const TLN_SELECTED_WORDS_PARAM: string = 'selectedWords'; /** * Param for iri of a genetic order of text versions. **/ export const TLN_TEXT_GENETIC_ORDER_PARAM: string = 'geneticOrder'; /** * Param for selected view option, e.g. 'Transkription', 'Faksimile', etc. **/ export const TLN_VIEW_OPTION_PARAM: string = 'viewMode'; /** * Param for multi instance zoom (i.e. tln-crossref and tln-fulltext). **/ export const TLN_MULTI_INSTANCE_ZOOM_PARAM: string = 'multiInstanceZoom'; /** * Param for zoom in tln-viewer. **/ export const TLN_ZOOM_PARAM: string = 'zoom'; diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/version-view/version-view.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/version-view/version-view.component.html index 53b5d4e..ee22a33 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-crossref/version-view/version-view.component.html +++ b/nietzsche-beta-app/src/app/tln-edition/tln-crossref/version-view/version-view.component.html @@ -1,9 +1,9 @@ {{version.title}}, - {{textUnity.number}}, {{textUnity.startLine}}-{{textUnity.endLine}} ; ,