1
+ /* Copyright 2019 Pascal Christoph (hbz) and others
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 the "License";
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ package org .metafacture .biblio .marc21 ;
17
+
18
+ import java .util .Collections ;
19
+
20
+ import org .metafacture .commons .XmlUtil ;
21
+ import org .metafacture .framework .FluxCommand ;
22
+ import org .metafacture .framework .MetafactureException ;
23
+ import org .metafacture .framework .ObjectReceiver ;
24
+ import org .metafacture .framework .StreamReceiver ;
25
+ import org .metafacture .framework .annotations .Description ;
26
+ import org .metafacture .framework .annotations .In ;
27
+ import org .metafacture .framework .annotations .Out ;
28
+ import org .metafacture .framework .helpers .DefaultStreamPipe ;
29
+
30
+ /**
31
+ * Encodes a stream into MARCXML.
32
+ *
33
+ * @author some Jan (Eberhardt) did almost all
34
+ * @author Pascal Christoph (dr0i) dug it up again
35
+ */
36
+
37
+ @ Description ("Encodes a stream into MARCXML." )
38
+ @ In (StreamReceiver .class )
39
+ @ Out (String .class )
40
+ @ FluxCommand ("encode-marcxml" )
41
+ public final class MarcXmlEncoder extends DefaultStreamPipe <ObjectReceiver <String >> {
42
+ private static final String ROOT_OPEN = "<marc:collection xmlns:marc=\" http://www.loc.gov/MARC21/slim\" xmlns:xsi=\" http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\" http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\" >" ;
43
+ private static final String ROOT_CLOSE = "</marc:collection>" ;
44
+
45
+ private static final String RECORD_OPEN = "<marc:record>" ;
46
+ private static final String RECORD_CLOSE = "</marc:record>" ;
47
+
48
+ private static final String CONTROLFIELD_OPEN_TEMPLATE = "<marc:controlfield tag=\" %s\" >" ;
49
+ private static final String CONTROLFIELD_CLOSE = "</marc:controlfield>" ;
50
+
51
+ private static final String DATAFIELD_OPEN_TEMPLATE = "<marc:datafield tag=\" %s\" ind1=\" %s\" ind2=\" %s\" >" ;
52
+ private static final String DATAFIELD_CLOSE = "</marc:datafield>" ;
53
+
54
+ private static final String SUBFIELD_OPEN_TEMPLATE = "<marc:subfield code=\" %s\" >" ;
55
+ private static final String SUBFIELD_CLOSE = "</marc:subfield>" ;
56
+
57
+ private static final String LEADER_OPEN_TEMPLATE = "<marc:leader>" ;
58
+ private static final String LEADER_CLOSE_TEMPLATE = "</marc:leader>" ;
59
+
60
+ private static final String NEW_LINE = "\n " ;
61
+ private static final String INDENT = "\t " ;
62
+
63
+ private static final String XML_DECLARATION_TEMPLATE = "<?xml version=\" %s\" encoding=\" %s\" ?>" ;
64
+
65
+ private final StringBuilder builder ;
66
+
67
+ private boolean atStreamStart ;
68
+
69
+ private boolean omitXmlDeclaration ;
70
+ private String xmlVersion ;
71
+ private String xmlEncoding ;
72
+
73
+ private String currentEntity ;
74
+ private int indentationLevel ;
75
+ private boolean formatted ;
76
+
77
+ public MarcXmlEncoder () {
78
+ this .builder = new StringBuilder ();
79
+ this .atStreamStart = true ;
80
+
81
+ this .omitXmlDeclaration = false ;
82
+ this .xmlVersion = "1.0" ;
83
+ this .xmlEncoding = "UTF-8" ;
84
+
85
+ this .currentEntity = "" ;
86
+
87
+ this .indentationLevel = 0 ;
88
+ this .formatted = true ;
89
+ }
90
+
91
+ public void omitXmlDeclaration (boolean omitXmlDeclaration ) {
92
+ this .omitXmlDeclaration = omitXmlDeclaration ;
93
+ }
94
+
95
+ public void setXmlVersion (String xmlVersion ) {
96
+ this .xmlVersion = xmlVersion ;
97
+ }
98
+
99
+ public void setXmlEncoding (String xmlEncoding ) {
100
+ this .xmlEncoding = xmlEncoding ;
101
+ }
102
+
103
+ /**
104
+ * Formats the resulting xml, by indentation.
105
+ *
106
+ * @param formatted
107
+ * True, if formatting is activated.
108
+ */
109
+ public void setFormatted (boolean formatted ) {
110
+ this .formatted = formatted ;
111
+ }
112
+
113
+ @ Override
114
+ public void startRecord (final String identifier ) {
115
+ if (atStreamStart ) {
116
+ if (!omitXmlDeclaration ) {
117
+ writeHeader ();
118
+ prettyPrintNewLine ();
119
+ }
120
+ writeRaw (ROOT_OPEN );
121
+ prettyPrintNewLine ();
122
+ incrementIndentationLevel ();
123
+ }
124
+ atStreamStart = false ;
125
+
126
+ prettyPrintIndentation ();
127
+ writeRaw (RECORD_OPEN );
128
+ prettyPrintNewLine ();
129
+
130
+ incrementIndentationLevel ();
131
+ }
132
+
133
+ @ Override
134
+ public void endRecord () {
135
+ decrementIndentationLevel ();
136
+ prettyPrintIndentation ();
137
+ writeRaw (RECORD_CLOSE );
138
+ prettyPrintNewLine ();
139
+ sendAndClearData ();
140
+ }
141
+
142
+ @ Override
143
+ public void startEntity (final String name ) {
144
+ currentEntity = name ;
145
+ if (!name .equals (Marc21EventNames .LEADER_ENTITY )) {
146
+ if (name .length () != 5 ) {
147
+ String message = String .format ("Entity too short." + "Got a string ('%s') of length %d."
148
+ + "Expected a length of 5 (field + indicators)." , name , name .length ());
149
+ throw new MetafactureException (message );
150
+ }
151
+
152
+ String tag = name .substring (0 , 3 );
153
+ String ind1 = name .substring (3 , 4 );
154
+ String ind2 = name .substring (4 , 5 );
155
+ prettyPrintIndentation ();
156
+ writeRaw (String .format (DATAFIELD_OPEN_TEMPLATE , tag , ind1 , ind2 ));
157
+ prettyPrintNewLine ();
158
+ incrementIndentationLevel ();
159
+ }
160
+ }
161
+
162
+ @ Override
163
+ public void endEntity () {
164
+ if (!currentEntity .equals (Marc21EventNames .LEADER_ENTITY )) {
165
+ decrementIndentationLevel ();
166
+ prettyPrintIndentation ();
167
+ writeRaw (DATAFIELD_CLOSE );
168
+ prettyPrintNewLine ();
169
+ }
170
+ currentEntity = "" ;
171
+ }
172
+
173
+ @ Override
174
+ public void literal (final String name , final String value ) {
175
+ if (currentEntity .equals ("" )) {
176
+ prettyPrintIndentation ();
177
+ writeRaw (String .format (CONTROLFIELD_OPEN_TEMPLATE , name ));
178
+ writeEscaped (value .trim ());
179
+ writeRaw (CONTROLFIELD_CLOSE );
180
+ prettyPrintNewLine ();
181
+ } else if (!currentEntity .equals (Marc21EventNames .LEADER_ENTITY )) {
182
+ prettyPrintIndentation ();
183
+ writeRaw (String .format (SUBFIELD_OPEN_TEMPLATE , name ));
184
+ writeEscaped (value .trim ());
185
+ writeRaw (SUBFIELD_CLOSE );
186
+ prettyPrintNewLine ();
187
+ } else {
188
+ if (name .equals (Marc21EventNames .LEADER_ENTITY )) {
189
+ prettyPrintIndentation ();
190
+ writeRaw (LEADER_OPEN_TEMPLATE + value + LEADER_CLOSE_TEMPLATE );
191
+ prettyPrintNewLine ();
192
+ }
193
+ }
194
+
195
+ }
196
+
197
+ @ Override
198
+ protected void onResetStream () {
199
+ if (!atStreamStart ) {
200
+ writeFooter ();
201
+ }
202
+ sendAndClearData ();
203
+ atStreamStart = true ;
204
+ }
205
+
206
+ @ Override
207
+ protected void onCloseStream () {
208
+ writeFooter ();
209
+ sendAndClearData ();
210
+ }
211
+
212
+ /** Increments the indentation level by one */
213
+ private void incrementIndentationLevel () {
214
+ indentationLevel += 1 ;
215
+ }
216
+
217
+ /** Decrements the indentation level by one */
218
+ private void decrementIndentationLevel () {
219
+ indentationLevel -= 1 ;
220
+ }
221
+
222
+ /** Adds a XML Header */
223
+ private void writeHeader () {
224
+ writeRaw (String .format (XML_DECLARATION_TEMPLATE , xmlVersion , xmlEncoding ));
225
+ }
226
+
227
+ /** Closes the root tag */
228
+ private void writeFooter () {
229
+ writeRaw (ROOT_CLOSE );
230
+ }
231
+
232
+ /** Writes a unescaped sequence */
233
+ private void writeRaw (final String str ) {
234
+ builder .append (str );
235
+ }
236
+
237
+ /** Writes a escaped sequence */
238
+ private void writeEscaped (final String str ) {
239
+ builder .append (XmlUtil .escape (str , false ));
240
+ }
241
+
242
+ private void prettyPrintIndentation () {
243
+ if (formatted ) {
244
+ String prefix = String .join ("" , Collections .nCopies (indentationLevel , INDENT ));
245
+ builder .append (prefix );
246
+ }
247
+ }
248
+
249
+ private void prettyPrintNewLine () {
250
+ if (formatted ) {
251
+ builder .append (NEW_LINE );
252
+ }
253
+ }
254
+
255
+ private void sendAndClearData () {
256
+ getReceiver ().process (builder .toString ());
257
+ builder .delete (0 , builder .length ());
258
+ }
259
+ }
0 commit comments