Fix: append : between prefix and name.
[marc4j.git] / src / org / marc4j / BaseMarcXmlWriter.java
1 package org.marc4j;
2
3 import java.io.IOException;
4 import java.io.Writer;
5 import java.util.Iterator;
6 import java.util.List;
7
8 import javax.xml.transform.OutputKeys;
9 import javax.xml.transform.Result;
10 import javax.xml.transform.Source;
11 import javax.xml.transform.TransformerFactory;
12 import javax.xml.transform.sax.SAXTransformerFactory;
13 import javax.xml.transform.sax.TransformerHandler;
14
15 import org.marc4j.converter.CharConverter;
16 import org.marc4j.marc.ControlField;
17 import org.marc4j.marc.DataField;
18 import org.marc4j.marc.Leader;
19 import org.marc4j.marc.Record;
20 import org.marc4j.marc.Subfield;
21 import org.xml.sax.SAXException;
22 import org.xml.sax.helpers.AttributesImpl;
23
24 import com.ibm.icu.text.Normalizer;
25
26 public class BaseMarcXmlWriter implements MarcWriter {
27
28   /**
29    * Character encoding. Default is UTF-8.
30    */
31   protected CharConverter converter = null;
32   protected Writer writer = null;
33   protected boolean indent = false;
34   protected TransformerHandler handler = null;
35   boolean normalize = false;
36   private String namespaceURI; 
37   private String collectionName;
38   private String prefix; 
39   private String qualifiedCollectionName;
40   private String recordName;
41   private String qualifiedRecordName;
42   private String leaderName;
43   private String qualifiedLeaderName;
44   private String subfieldTemplate;
45   private String qualifiedSubfieldTemplate;
46   private String datafieldTemplate;
47   private String qualifiedDatafieldTemplate;
48   private String controlfieldTemplate;
49   private String qualifiedControlfieldTemplate;
50   private boolean useTurboMarc = true;
51   
52   /**
53    * Returns the character converter.
54    * 
55    * @return CharConverter the character converter
56    */
57   public CharConverter getConverter() {
58       return converter;
59   }
60
61   /**
62    * Sets the character converter.
63    * 
64    * @param converter
65    *            the character converter
66    */
67   public void setConverter(CharConverter converter) {
68       this.converter = converter;
69   }
70
71   /**
72    * Writes a Record object to the result.
73    * 
74    * @param record -
75    *            the <code>Record</code> object
76    * @throws SAXException
77    */
78   public void write(Record record) {
79       try {
80           toXml(record);
81       } catch (SAXException e) {
82           throw new MarcException("SAX error occured while writing record", e);
83       }
84   }
85
86   public void close() {
87         writeEndDocument();
88         try {
89           if (writer != null)
90             writer.close();
91         } catch (IOException e) {
92                 throw new MarcException(e.getMessage(), e);
93         }
94   }
95
96   /**
97    * If set to true this writer will perform Unicode normalization on data
98    * elements using normalization form C (NFC). The default is false.
99    * 
100    * The implementation used is ICU4J 2.6. This version is based on Unicode
101    * 4.0.
102    * 
103    * @param normalize
104    *            true if this writer performs Unicode normalization, false
105    *            otherwise
106    */
107   public void setUnicodeNormalization(boolean normalize) {
108       this.normalize = normalize;
109   }
110
111   /**
112    * Returns true if this writer will perform Unicode normalization, false
113    * otherwise.
114    * 
115    * @return boolean - true if this writer performs Unicode normalization,
116    *         false otherwise.
117    */
118   public boolean getUnicodeNormalization() {
119       return normalize;
120   }
121
122   protected void setHandler(Result result, Source stylesheet) throws MarcException {
123       try {
124           TransformerFactory factory = TransformerFactory.newInstance();
125           if (!factory.getFeature(SAXTransformerFactory.FEATURE))
126               throw new UnsupportedOperationException(
127                       "SAXTransformerFactory is not supported");
128   
129           SAXTransformerFactory saxFactory = (SAXTransformerFactory) factory;
130           if (stylesheet == null)
131               handler = saxFactory.newTransformerHandler();
132           else
133               handler = saxFactory.newTransformerHandler(stylesheet);
134           handler.getTransformer()
135                   .setOutputProperty(OutputKeys.METHOD, "xml");
136           handler.setResult(result);
137   
138       } catch (Exception e) {
139           throw new MarcException(e.getMessage(), e);
140       }
141   }
142
143   /**
144    * Writes the root start tag to the result.
145    * 
146    * @throws SAXException
147    */
148   protected void writeStartDocument() {
149       try {
150           AttributesImpl atts = new AttributesImpl();
151           handler.startDocument();
152           // Add a new line after <?xml ?> 
153           handler.ignorableWhitespace("\n".toCharArray(), 0, 1);
154           // The next line duplicates the namespace declaration for Marc XML
155           handler.startPrefixMapping(prefix, namespaceURI);
156           // add namespace declaration using attribute - need better solution
157           //atts.addAttribute(namespaceURI, "xmlns", "xmlns:" + prefix, "CDATA", namespaceURI);            
158           handler.startElement(namespaceURI, collectionName, qualifiedCollectionName, atts);
159       } catch (SAXException e) {
160           throw new MarcException(
161                   "SAX error occured while writing start document", e);
162       }
163   }
164
165   /**
166    * Writes the root end tag to the result.
167    * 
168    * @throws SAXException
169    */
170   protected void writeEndDocument() {
171       try {
172           if (indent)
173               handler.ignorableWhitespace("\n".toCharArray(), 0, 1);
174   
175           handler.endElement(namespaceURI, collectionName, qualifiedCollectionName);
176           handler.endPrefixMapping("");
177           handler.endDocument();
178       } catch (SAXException e) {
179           throw new MarcException(
180                   "SAX error occured while writing end document", e);
181       }
182   }
183
184   /**
185    * Returns true if indentation is active, false otherwise.
186    * 
187    * @return boolean
188    */
189   public boolean hasIndent() {
190       return indent;
191   }
192
193   /**
194    * Activates or deactivates indentation. Default value is false.
195    * 
196    * @param indent
197    */
198   public void setIndent(boolean indent) {
199       this.indent = indent;
200   }
201
202   protected char[] getDataElement(String data) {
203       String dataElement = null;
204       if (converter == null)
205           return data.toCharArray();
206       dataElement = converter.convert(data);
207       if (normalize)
208           dataElement = Normalizer.normalize(dataElement, Normalizer.NFC);
209       return dataElement.toCharArray();
210   }
211
212   public String getNamespaceURI() {
213     return namespaceURI;
214   }
215
216   public void setNamespaceURI(String namespaceURI) {
217     this.namespaceURI = namespaceURI;
218   }
219
220   public String getCollectionName() {
221     return collectionName;
222   }
223
224   public void setCollectionName(String collectionName) {
225     this.collectionName = collectionName;
226     qualifiedCollectionName = setPrefixedName(collectionName);
227   }
228   
229   private String setPrefixedName(String name) {
230     if (prefix != null && name != null)
231       return prefix + ":" + name;
232     return name;
233   }
234
235   protected void handleDataField(DataField field) throws SAXException {
236     AttributesImpl atts = new AttributesImpl();
237     if (!useTurboMarc)
238       atts.addAttribute("", "tag", "tag", "CDATA", field.getTag());
239     atts.addAttribute("", "ind1", "ind1", "CDATA", String.valueOf(field.getIndicator1()));
240     atts.addAttribute("", "ind2", "ind2", "CDATA", String.valueOf(field.getIndicator2()));
241
242     if (indent)
243         handler.ignorableWhitespace("\n    ".toCharArray(), 0, 5);
244     StringBuffer elementName = new StringBuffer(datafieldTemplate);
245     StringBuffer qElementName = new StringBuffer(qualifiedDatafieldTemplate);
246     if (useTurboMarc) {
247       elementName.append(field.getTag());
248       qElementName.append(field.getTag());
249     }
250     handler.startElement(namespaceURI, elementName.toString(), qElementName.toString(), atts);
251     
252     handleSubfields(field.getSubfields());
253     if (indent)
254       handler.ignorableWhitespace("\n    ".toCharArray(), 0, 5);
255     handler.endElement(namespaceURI, elementName.toString(), qElementName.toString());
256   }
257
258   protected void handleSubfields(List<Subfield> subfields) throws SAXException {
259     Iterator<Subfield> si = subfields.iterator();
260     while (si.hasNext()) {
261         Subfield subfield = (Subfield) si.next();
262         handleSubfield(subfield);
263     }
264   }
265
266   protected void handleSubfield(Subfield subfield) throws SAXException {
267     AttributesImpl atts = new AttributesImpl();
268     StringBuffer subfieldName = new StringBuffer(subfieldTemplate); 
269     StringBuffer qSubfieldName = new StringBuffer(qualifiedSubfieldTemplate);
270     
271     char code = subfield.getCode(); 
272     // if [a-zA-Z0-9] append to  elementName, otherwise use a attribute
273     if (code >= '0' && code <= '9' ||
274         code >= 'a' && code <= 'z' ||
275         code >= 'A' && code <= 'Z') {
276       subfieldName.append(code);
277       qSubfieldName.append(code);
278     }
279     else {
280       atts = new AttributesImpl();
281       atts.addAttribute("", "code", "code", "CDATA", String
282           .valueOf(subfield.getCode()));
283     }
284     if (indent)
285         handler.ignorableWhitespace("\n      ".toCharArray(), 0, 7);
286
287     handler.startElement(namespaceURI, subfieldName.toString(), qSubfieldName.toString(), atts);
288     char[] temp = getDataElement(subfield.getData());
289     handler.characters(temp, 0, temp.length);
290     handler.endElement(namespaceURI, subfieldName.toString(), qSubfieldName.toString());
291   }
292
293   protected void toXml(Record record) throws SAXException {
294       AttributesImpl atts = new AttributesImpl();
295       if (indent)
296           handler.ignorableWhitespace("\n  ".toCharArray(), 0, 3);
297   
298       handler.startElement(namespaceURI, recordName, qualifiedRecordName, atts);
299   
300       if (indent)
301           handler.ignorableWhitespace("\n    ".toCharArray(), 0, 5);
302   
303       handleLeader(record, atts);
304   
305       handleControlfields(record.getControlFields());
306       Iterator<DataField> di = record.getDataFields().iterator();
307       while (di.hasNext()) {
308         DataField field = di.next();
309         handleDataField(field);
310       }
311   
312       if (indent)
313           handler.ignorableWhitespace("\n  ".toCharArray(), 0, 3);
314   
315       handler.endElement(namespaceURI, recordName, qualifiedRecordName);
316   }
317
318   protected void handleControlfields(List<ControlField> controlFields) throws SAXException {
319     Iterator<ControlField> ci = controlFields.iterator();
320     while (ci.hasNext()) {
321         ControlField field = (ControlField) ci.next();
322         handleControlField(field);
323     }
324   }
325
326   protected void handleLeader(Record record, AttributesImpl atts) throws SAXException {
327     char[] temp;
328     handler.startElement(namespaceURI, leaderName, qualifiedLeaderName, atts);
329     Leader leader = record.getLeader();
330     temp = leader.toString().toCharArray();
331     handler.characters(temp, 0, temp.length);
332     handler.endElement(namespaceURI, leaderName, qualifiedLeaderName);
333   }
334
335   protected void handleControlField(ControlField field) throws SAXException {
336     AttributesImpl atts = new AttributesImpl();
337     
338     if (!useTurboMarc) 
339       atts.addAttribute("", "tag", "tag", "CDATA", field.getTag());
340
341     if (indent)
342         handler.ignorableWhitespace("\n    ".toCharArray(), 0, 5);
343     StringBuffer elementName = new StringBuffer(controlfieldTemplate);
344     StringBuffer qElementName = new StringBuffer(qualifiedControlfieldTemplate);
345     if (useTurboMarc) {
346       elementName.append(field.getTag());
347       qElementName.append(field.getTag());
348     }
349     handler.startElement(namespaceURI, elementName.toString(), qElementName.toString(), atts);
350     char[] temp = getDataElement(field.getData());
351     handler.characters(temp, 0, temp.length);
352     handler.endElement(namespaceURI, elementName.toString(), qElementName.toString());
353   }
354
355   public String getPrefix() {
356     return prefix;
357   }
358
359   public void setPrefix(String prefix) {
360     this.prefix = prefix;    
361     // Update the prefixed names 
362     qualifiedCollectionName = setPrefixedName(collectionName);
363     qualifiedRecordName = setPrefixedName(recordName);
364     qualifiedControlfieldTemplate = setPrefixedName(controlfieldTemplate);
365     qualifiedLeaderName = setPrefixedName(leaderName);
366     qualifiedDatafieldTemplate = setPrefixedName(datafieldTemplate);
367     qualifiedDatafieldTemplate = setPrefixedName(datafieldTemplate);
368     qualifiedSubfieldTemplate = setPrefixedName(subfieldTemplate);
369   }
370
371   public String getRecordName() {
372     return recordName;
373   }
374
375   public void setRecordName(String recordName) {
376     this.recordName = recordName;
377     qualifiedRecordName = setPrefixedName(recordName);
378   }
379
380   public String getLeaderName() {
381     return leaderName;
382   }
383
384   public void setLeaderName(String leaderName) {
385     this.leaderName = leaderName;
386     qualifiedLeaderName = setPrefixedName(leaderName);
387   }
388
389   public String getSubfieldTemplate() {
390     return subfieldTemplate;
391   }
392
393   public void setSubfieldTemplate(String subfieldTemplate) {
394     this.subfieldTemplate = subfieldTemplate;
395     qualifiedSubfieldTemplate = setPrefixedName(subfieldTemplate);
396   }
397
398   public String getDatafieldTemplate() {
399     return datafieldTemplate;
400   }
401
402   public void setDatafieldTemplate(String datafieldTemplate) {
403     this.datafieldTemplate = datafieldTemplate;
404     qualifiedDatafieldTemplate = setPrefixedName(datafieldTemplate);
405   }
406
407   public String getControlfieldTemplate() {
408     return controlfieldTemplate;
409   }
410
411   public void setControlfieldTemplate(String controlfieldTemplate) {
412     this.controlfieldTemplate = controlfieldTemplate;
413     qualifiedControlfieldTemplate = setPrefixedName(controlfieldTemplate);
414   }
415
416   public boolean isUseTurboMarc() {
417     return useTurboMarc;
418   }
419
420   public void setUseTurboMarc(boolean useTurboMarc) {
421     this.useTurboMarc = useTurboMarc;
422   }
423 }