LP1852782 Prevents data fields swapping to control fields
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / marc-edit / marcrecord.ts
1 import {EventEmitter} from '@angular/core';
2
3 /* Wrapper class for our external MARC21.Record JS library. */
4
5 declare var MARC21;
6
7 // MARC breaker delimiter
8 const DELIMITER = '$';
9
10 export interface MarcSubfield    // code, value, position
11     extends Array<string|number> { 0: string; 1: string; 2: number; }
12
13 // Only contains the attributes/methods we need so far.
14 export interface MarcField {
15     fieldId?: number;
16     data?: string;
17     tag?: string;
18     ind1?: string;
19     ind2?: string;
20     subfields?: MarcSubfield[];
21
22     // Fields are immutable when it comes to controlfield vs.
23     // data field.  Stamp the value when stamping field IDs.
24     isCtrlField: boolean;
25
26     // Pass-through to marcrecord.js
27     isControlfield(): boolean;
28
29     deleteExactSubfields(...subfield: MarcSubfield[]): number;
30 }
31
32 export class MarcRecord {
33
34     id: number; // Database ID when known.
35     deleted: boolean;
36     record: any; // MARC21.Record object
37     breakerText: string;
38
39     // Let clients know some fixed field shuffling may have occured.
40     // Emits the fixed field code.
41     fixedFieldChange: EventEmitter<string>;
42
43     get leader(): string {
44         return this.record.leader;
45     }
46
47     set leader(l: string) {
48         this.record.leader = l;
49     }
50
51     get fields(): MarcField[] {
52        return this.record.fields;
53     }
54
55     set fields(f: MarcField[]) {
56         this.record.fields = f;
57     }
58
59     constructor(xml: string) {
60         this.record = new MARC21.Record({marcxml: xml, delimiter: DELIMITER});
61         this.breakerText = this.record.toBreaker();
62         this.fixedFieldChange = new EventEmitter<string>();
63     }
64
65     toXml(): string {
66         return this.record.toXmlString();
67     }
68
69     toBreaker(): string {
70         return this.record.toBreaker();
71     }
72
73     recordType(): string {
74         return this.record.recordType();
75     }
76
77     absorbBreakerChanges() {
78         this.record = new MARC21.Record(
79             {marcbreaker: this.breakerText, delimiter: DELIMITER});
80     }
81
82     extractFixedField(fieldCode: string): string {
83         return this.record.extractFixedField(fieldCode);
84     }
85
86     setFixedField(fieldCode: string, fieldValue: string): string {
87         const response = this.record.setFixedField(fieldCode, fieldValue);
88         this.fixedFieldChange.emit(fieldCode);
89         return response;
90     }
91
92     // Give each field an identifier so it may be referenced later.
93     stampFieldIds() {
94         this.fields.forEach(f => this.stampFieldId(f));
95     }
96
97     // Stamp field IDs the the initial isCtrlField state.
98     stampFieldId(field: MarcField) {
99         if (!field.fieldId) {
100             field.fieldId = Math.floor(Math.random() * 10000000);
101         }
102
103         if (field.isCtrlField === undefined) {
104             field.isCtrlField = field.isControlfield();
105         }
106     }
107
108     field(spec: string, wantArray?: boolean): MarcField | MarcField[] {
109         return this.record.field(spec, wantArray);
110     }
111
112     insertFieldsBefore(field: MarcField, ...newFields: MarcField[]) {
113         this.record.insertFieldsBefore.apply(
114             this.record, [field].concat(newFields));
115         this.stampFieldIds();
116     }
117
118     insertFieldsAfter(field: MarcField, ...newFields: MarcField[]) {
119         this.record.insertFieldsAfter.apply(
120             this.record, [field].concat(newFields));
121         this.stampFieldIds();
122     }
123
124     insertOrderedFields(...newFields: MarcField[]) {
125         this.record.insertOrderedFields.apply(this.record, newFields);
126         this.stampFieldIds();
127     }
128
129     generate008(): MarcField {
130         return this.record.generate008();
131     }
132
133
134     deleteFields(...fields: MarcField[]) {
135         this.record.deleteFields.apply(this.record, fields);
136     }
137
138     getField(id: number): MarcField {
139         return this.fields.filter(f => f.fieldId === id)[0];
140     }
141
142     getPreviousField(id: number): MarcField {
143         for (let idx = 0; idx < this.fields.length; idx++) {
144             if (this.fields[idx].fieldId === id) {
145                 return this.fields[idx - 1];
146             }
147         }
148     }
149
150     getNextField(id: number): MarcField {
151         for (let idx = 0; idx < this.fields.length; idx++) {
152             if (this.fields[idx].fieldId === id) {
153                 return this.fields[idx + 1];
154             }
155         }
156     }
157
158     // Turn an field-ish object into a proper MARC.Field
159     newField(props: any): MarcField {
160         const field = new MARC21.Field(props);
161         this.stampFieldId(field);
162         return field;
163     }
164
165     cloneField(field: any): MarcField {
166         const props: any = {tag: field.tag};
167
168         if (field.isControlfield()) {
169             props.data = field.data;
170
171         } else {
172             props.ind1 = field.ind1;
173             props.ind2 = field.ind2;
174             props.subfields = this.cloneSubfields(field.subfields);
175         }
176
177         return this.newField(props);
178     }
179
180     cloneSubfields(subfields: MarcSubfield[]): MarcSubfield[] {
181         const root = [];
182         subfields.forEach(sf => root.push([].concat(sf)));
183         return root;
184     }
185 }
186