OSDN Git Service

use String constants.
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / xml / AbstractXmlExporter.java
1 /*
2  * abstract xml exporter
3  *
4  * License : The MIT License
5  * Copyright(c) 2013 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.xml;
9
10 import java.io.IOException;
11
12 /**
13  * Appendable実装に依存したXMLエクスポータの半実装。
14  * UCS4は未サポート。
15  */
16 abstract class AbstractXmlExporter implements XmlExporter{
17
18     /** デフォルトの改行文字列。 */
19     private static final String DEF_NL = "\n";       // 0x0a(LF)
20     /** デフォルトのインデント単位。 */
21     private static final String DEF_INDENT_UNIT = "\u0020\u0020"; // ␣␣
22
23     private static final char CH_SP     = '\u0020';    // ␣
24     private static final char CH_YEN    = '\u00a5';    // ¥
25     private static final char CH_BSLASH = (char)0x005c; // \
26     private static final char CH_DQ     = '\u0022';    // "
27     private static final char CH_SQ     = (char)0x0027; // '
28     private static final char CH_EQ     = '=';          // =
29     private static final char CH_LT     = '<';
30     private static final char CH_GT     = '>';
31
32     private static final String COMM_START = "<!--";
33     private static final String COMM_END   =   "-->";
34
35     private static final String REF_HEX = "&#x";
36     private static final int HEX_EXP = 4;    // 2 ** 4 == 16
37     private static final int MASK_1HEX = (1 << HEX_EXP) - 1;  // 0b00001111
38     private static final int MAX_OCTET = (1 << Byte.SIZE) - 1;   // 0xff
39     private static final char[] HEXCHAR_TABLE = {
40         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
41         'A', 'B', 'C', 'D', 'E', 'F',
42     };
43
44     static{
45         assert HEX_EXP * 2 == Byte.SIZE;
46         assert HEXCHAR_TABLE.length == (1 << HEX_EXP);
47     }
48
49
50     private boolean basicLatinOnlyOut = true;
51     private String newline = DEF_NL;
52     private String indentUnit = DEF_INDENT_UNIT;
53     private int indentNest = 0;
54
55
56     /**
57      * コンストラクタ。
58      */
59     protected AbstractXmlExporter(){
60         super();
61         return;
62     }
63
64
65     /**
66      * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。
67      *
68      * <p>※ Basic-Latinには各種制御文字も含まれる。
69      *
70      * @param ch 判定対象文字
71      * @return Basic-Latin文字ならtrue
72      * <a href="http://www.unicode.org/charts/PDF/U0000.pdf">
73      * Unicode 6.2 Controls and Basic Latin
74      * </a>
75      */
76     protected static boolean isBasicLatin(char ch){
77         if('\u0000' <= ch && ch <= '\u007f'){
78             return true;
79         }
80         return false;
81     }
82
83
84     /**
85      * {@inheritDoc}
86      * @param ch {@inheritDoc}
87      * @return {@inheritDoc}
88      * @throws IOException {@inheritDoc}
89      */
90     @Override
91     public abstract Appendable append(char ch) throws IOException;
92
93     /**
94      * {@inheritDoc}
95      * @param seq {@inheritDoc}
96      * @return {@inheritDoc}
97      * @throws IOException {@inheritDoc}
98      */
99     @Override
100     public abstract Appendable append(CharSequence seq) throws IOException;
101
102     /**
103      * {@inheritDoc}
104      * @param seq {@inheritDoc}
105      * @param start {@inheritDoc}
106      * @param end {@inheritDoc}
107      * @return {@inheritDoc}
108      * @throws IOException {@inheritDoc}
109      */
110     @Override
111     public abstract Appendable append(CharSequence seq, int start, int end)
112             throws IOException;
113
114     /**
115      * {@inheritDoc}
116      * @throws IOException {@inheritDoc}
117      */
118     @Override
119     public abstract void flush() throws IOException;
120
121     /**
122      * {@inheritDoc}
123      * @throws IOException {@inheritDoc}
124      */
125     @Override
126     public abstract void close() throws IOException;
127
128
129     /**
130      * {@inheritDoc}
131      * @param ch {@inheritDoc}
132      * @return {@inheritDoc}
133      * @throws IOException {@inheritDoc}
134      */
135     @Override
136     public XmlExporter putRawCh(char ch) throws IOException{
137         append(ch);
138         return this;
139     }
140
141     /**
142      * {@inheritDoc}
143      * @param seq {@inheritDoc}
144      * @return {@inheritDoc}
145      * @throws IOException {@inheritDoc}
146      */
147     @Override
148     public XmlExporter putRawText(CharSequence seq)
149             throws IOException{
150         append(seq);
151         return this;
152     }
153
154     /**
155      * {@inheritDoc}
156      * @return {@inheritDoc}
157      * @throws IOException {@inheritDoc}
158      */
159     @Override
160     public XmlExporter sp() throws IOException{
161         putRawCh(CH_SP);
162         return this;
163     }
164
165     /**
166      * {@inheritDoc}
167      * @param count {@inheritDoc}
168      * @return {@inheritDoc}
169      * @throws IOException {@inheritDoc}
170      */
171     @Override
172     public XmlExporter sp(int count) throws IOException{
173         for(int ct = 1; ct <= count; ct++){
174             sp();
175         }
176         return this;
177     }
178
179     /**
180      * {@inheritDoc}
181      * @return {@inheritDoc}
182      */
183     @Override
184     public String getNewLine(){
185         return this.newline;
186     }
187
188     /**
189      * {@inheritDoc}
190      * @param newLine {@inheritDoc}
191      * @throws NullPointerException {@inheritDoc}
192      */
193     @Override
194     public void setNewLine(String newLine) throws NullPointerException{
195         if(newLine == null) throw new NullPointerException();
196         this.newline = newLine;
197         return;
198     }
199
200     /**
201      * {@inheritDoc}
202      * @return {@inheritDoc}
203      * @throws IOException {@inheritDoc}
204      */
205     @Override
206     public XmlExporter ln() throws IOException{
207         putRawText(getNewLine());
208         return this;
209     }
210
211     /**
212      * {@inheritDoc}
213      * @param count {@inheritDoc}
214      * @return {@inheritDoc}
215      * @throws IOException {@inheritDoc}
216      */
217     @Override
218     public XmlExporter ln(int count) throws IOException{
219         for(int ct = 1; ct <= count; ct++){
220             ln();
221         }
222         return this;
223     }
224
225     /**
226      * {@inheritDoc}
227      * @return {@inheritDoc}
228      */
229     @Override
230     public String getIndentUnit(){
231         return this.indentUnit;
232     }
233
234     /**
235      * {@inheritDoc}
236      * @param indUnit {@inheritDoc}
237      * @throws NullPointerException {@inheritDoc}
238      */
239     @Override
240     public void setIndentUnit(String indUnit) throws NullPointerException{
241         if(indUnit == null) throw new NullPointerException();
242         this.indentUnit = indUnit;
243         return;
244     }
245
246     /**
247      * {@inheritDoc}
248      */
249     @Override
250     public void pushNest(){
251         this.indentNest++;
252         return;
253     }
254
255     /**
256      * {@inheritDoc}
257      */
258     @Override
259     public void popNest(){
260         this.indentNest--;
261         if(this.indentNest < 0) this.indentNest = 0;
262         return;
263     }
264
265     /**
266      * {@inheritDoc}
267      * @return {@inheritDoc}
268      */
269     @Override
270     public int getIndentLevel(){
271         return this.indentNest;
272     }
273
274     /**
275      * {@inheritDoc}
276      * @return {@inheritDoc}
277      * @throws IOException {@inheritDoc}
278      */
279     @Override
280     public XmlExporter ind() throws IOException{
281         int level = getIndentLevel();
282         for(int ct = 1; ct <= level; ct++){
283             putRawText(getIndentUnit());
284         }
285         return this;
286     }
287
288     /**
289      * {@inheritDoc}
290      * @return {@inheritDoc}
291      */
292     @Override
293     public boolean isBasicLatinOnlyOut(){
294         return this.basicLatinOnlyOut;
295     }
296
297     /**
298      * {@inheritDoc}
299      * @param bool {@inheritDoc}
300      */
301     @Override
302     public void setBasicLatinOnlyOut(boolean bool){
303         this.basicLatinOnlyOut = bool;
304         return;
305     }
306
307     /**
308      * {@inheritDoc}
309      * @param ch {@inheritDoc}
310      * @return {@inheritDoc}
311      * @throws IOException {@inheritDoc}
312      */
313     @Override
314     public XmlExporter putCharRef2Hex(char ch) throws IOException{
315         if(ch > MAX_OCTET) return putCharRef4Hex(ch);
316
317         int ibits = ch;   // 常に正なので符号拡張なし
318
319         int idx4 = ibits & MASK_1HEX;
320         ibits >>= HEX_EXP;
321         int idx3 = ibits & MASK_1HEX;
322
323         char hex3 = HEXCHAR_TABLE[idx3];
324         char hex4 = HEXCHAR_TABLE[idx4];
325
326         putRawText(REF_HEX).putRawCh(hex3).putRawCh(hex4)
327                            .putRawCh(';');
328
329         return this;
330     }
331
332     /**
333      * {@inheritDoc}
334      * @param ch {@inheritDoc}
335      * @return {@inheritDoc}
336      * @throws IOException {@inheritDoc}
337      */
338     @Override
339     public XmlExporter putCharRef4Hex(char ch) throws IOException{
340         int ibits = ch;   // 常に正なので符号拡張なし
341
342         int idx4 = ibits & MASK_1HEX;
343         ibits >>= HEX_EXP;
344         int idx3 = ibits & MASK_1HEX;
345         ibits >>= HEX_EXP;
346         int idx2 = ibits & MASK_1HEX;
347         ibits >>= HEX_EXP;
348         int idx1 = ibits & MASK_1HEX;
349
350         char hex1 = HEXCHAR_TABLE[idx1];
351         char hex2 = HEXCHAR_TABLE[idx2];
352         char hex3 = HEXCHAR_TABLE[idx3];
353         char hex4 = HEXCHAR_TABLE[idx4];
354
355         putRawText(REF_HEX).putRawCh(hex1).putRawCh(hex2)
356                            .putRawCh(hex3).putRawCh(hex4)
357                            .putRawCh(';');
358
359         return this;
360     }
361
362     /**
363      * {@inheritDoc}
364      * @param ch {@inheritDoc}
365      * @return {@inheritDoc}
366      * @throws IOException {@inheritDoc}
367      */
368     @Override
369     public XmlExporter putCh(char ch) throws IOException{
370         if(Character.isISOControl(ch)){
371             putCharRef2Hex(ch);
372             return this;
373         }
374
375         String escTxt;
376         switch(ch){
377         case '&':   escTxt = "&amp;";  break;
378         case CH_LT: escTxt = "&lt;";   break;
379         case CH_GT: escTxt = "&gt;";   break;
380         case CH_DQ: escTxt = "&quot;"; break;
381         case CH_SQ: escTxt = "&apos;"; break;
382         default:    escTxt = null;     break;
383         }
384
385         if(escTxt != null){
386             putRawText(escTxt);
387         }else{
388             putRawCh(ch);
389         }
390
391         return this;
392     }
393
394     /**
395      * {@inheritDoc}
396      * @param content {@inheritDoc}
397      * @return {@inheritDoc}
398      * @throws IOException {@inheritDoc}
399      */
400     @Override
401     public XmlExporter putContent(CharSequence content)
402             throws IOException{
403         int length = content.length();
404
405         char prev = '\0';
406         for(int pos = 0; pos < length; pos++){
407             char ch = content.charAt(pos);
408
409             if( isBasicLatinOnlyOut() && ! isBasicLatin(ch) ){
410                 putCharRef4Hex(ch);
411             }else if(ch == CH_YEN){
412                 putRawCh(CH_BSLASH);
413             }else if(Character.isSpaceChar(ch)){
414                 if(ch == CH_SP && prev != CH_SP){
415                     putRawCh(ch);
416                 }else{
417                     putCharRef2Hex(ch);
418                 }
419             }else{
420                 putCh(ch);
421             }
422
423             prev = ch;
424         }
425
426         return this;
427     }
428
429     /**
430      * {@inheritDoc}
431      * @param comment {@inheritDoc}
432      * @return {@inheritDoc}
433      * @throws IOException {@inheritDoc}
434      */
435     @Override
436     public XmlExporter putCommentContent(CharSequence comment)
437             throws IOException{
438         int length = comment.length();
439
440         char prev = '\0';
441         for(int pos = 0; pos < length; pos++){
442             char ch = comment.charAt(pos);
443
444             if(ch == '\n'){
445                 ln();
446             }else if('\u0000' <= ch && ch <= '\u001f'){
447                 putRawCh((char)('\u2400' + ch));
448             }else if(ch == '\u007f'){
449                 putRawCh('\u2421');
450             }else if(prev == '-' && ch == '-'){
451                 sp().putRawCh(ch);
452             }else{
453                 putRawCh(ch);
454             }
455
456             prev = ch;
457         }
458
459         return this;
460     }
461
462     /**
463      * {@inheritDoc}
464      * @param comment {@inheritDoc}
465      * @return {@inheritDoc}
466      * @throws IOException {@inheritDoc}
467      */
468     @Override
469     public XmlExporter putLineComment(CharSequence comment)
470             throws IOException{
471         putRawText(COMM_START).sp();
472         putCommentContent(comment);
473         sp().putRawText(COMM_END);
474         return this;
475     }
476
477     /**
478      * {@inheritDoc}
479      * @param comment {@inheritDoc}
480      * @return {@inheritDoc}
481      * @throws IOException {@inheritDoc}
482      */
483     @Override
484     public XmlExporter putBlockComment(CharSequence comment)
485             throws IOException{
486         putRawText(COMM_START).ln();
487
488         putCommentContent(comment);
489
490         int commentLength = comment.length();
491         if(commentLength > 0){
492             char lastCh = comment.charAt(commentLength - 1);
493             if(lastCh != '\n'){
494                 ln();
495             }
496         }
497
498         putRawText(COMM_END).ln();
499
500         return this;
501     }
502
503     /**
504      * {@inheritDoc}
505      * @param tagName {@inheritDoc}
506      * @return {@inheritDoc}
507      * @throws IOException {@inheritDoc}
508      */
509     @Override
510     public XmlExporter putOpenSTag(CharSequence tagName)
511             throws IOException{
512         putRawCh(CH_LT);
513         putRawText(tagName);
514         return this;
515     }
516
517     /**
518      * {@inheritDoc}
519      * @return {@inheritDoc}
520      * @throws IOException {@inheritDoc}
521      */
522     @Override
523     public XmlExporter putCloseSTag()
524             throws IOException{
525         putRawCh(CH_GT);
526         return this;
527     }
528
529     /**
530      * {@inheritDoc}
531      * @param tagName {@inheritDoc}
532      * @return {@inheritDoc}
533      * @throws IOException {@inheritDoc}
534      */
535     @Override
536     public XmlExporter putSimpleSTag(CharSequence tagName)
537             throws IOException{
538         putRawCh(CH_LT);
539         putRawText(tagName);
540         putRawCh(CH_GT);
541         return this;
542     }
543
544     /**
545      * {@inheritDoc}
546      * @param tagName {@inheritDoc}
547      * @return {@inheritDoc}
548      * @throws IOException {@inheritDoc}
549      */
550     @Override
551     public XmlExporter putETag(CharSequence tagName)
552             throws IOException{
553         putRawText("</");
554         putRawText(tagName);
555         putRawCh(CH_GT);
556         return this;
557     }
558
559     /**
560      * {@inheritDoc}
561      * @param tagName {@inheritDoc}
562      * @return {@inheritDoc}
563      * @throws IOException {@inheritDoc}
564      */
565     @Override
566     public XmlExporter putSimpleEmpty(CharSequence tagName)
567             throws IOException{
568         putRawCh(CH_LT);
569         putRawText(tagName).sp();
570         putCloseEmpty();
571         return this;
572     }
573
574     /**
575      * {@inheritDoc}
576      * @return {@inheritDoc}
577      * @throws IOException {@inheritDoc}
578      */
579     @Override
580     public XmlExporter putCloseEmpty()
581             throws IOException{
582         putRawText("/>");
583         return this;
584     }
585
586     /**
587      * {@inheritDoc}
588      * @param iVal {@inheritDoc}
589      * @return {@inheritDoc}
590      * @throws IOException {@inheritDoc}
591      */
592     @Override
593     public XmlExporter putXsdInt(int iVal) throws IOException{
594         String value = DatatypeIo.printInt(iVal);
595         putRawText(value);
596         return this;
597     }
598
599     /**
600      * {@inheritDoc}
601      * @param fVal {@inheritDoc}
602      * @return {@inheritDoc}
603      * @throws IOException {@inheritDoc}
604      */
605     @Override
606     public XmlExporter putXsdFloat(float fVal) throws IOException{
607         String value = DatatypeIo.printFloat(fVal);
608         putRawText(value);
609         return this;
610     }
611
612     /**
613      * {@inheritDoc}
614      * @param attrName {@inheritDoc}
615      * @param iVal {@inheritDoc}
616      * @return {@inheritDoc}
617      * @throws IOException {@inheritDoc}
618      */
619     @Override
620     public XmlExporter putIntAttr(CharSequence attrName,
621                                         int iVal)
622             throws IOException{
623         putRawText(attrName).putRawCh(CH_EQ);
624
625         putRawCh(CH_DQ);
626         putXsdInt(iVal);
627         putRawCh(CH_DQ);
628
629         return this;
630     }
631
632     /**
633      * {@inheritDoc}
634      * @param attrName {@inheritDoc}
635      * @param fVal {@inheritDoc}
636      * @return {@inheritDoc}
637      * @throws IOException {@inheritDoc}
638      */
639     @Override
640     public XmlExporter putFloatAttr(CharSequence attrName,
641                                            float fVal)
642             throws IOException{
643         putRawText(attrName).putRawCh(CH_EQ);
644
645         putRawCh(CH_DQ);
646         putXsdFloat(fVal);
647         putRawCh(CH_DQ);
648
649         return this;
650     }
651
652     /**
653      * {@inheritDoc}
654      * @param attrName {@inheritDoc}
655      * @param content {@inheritDoc}
656      * @return {@inheritDoc}
657      * @throws IOException {@inheritDoc}
658      */
659     @Override
660     public XmlExporter putAttr(CharSequence attrName,
661                                      CharSequence content)
662             throws IOException{
663         putRawText(attrName).putRawCh(CH_EQ);
664
665         putRawCh(CH_DQ);
666         putContent(content);
667         putRawCh(CH_DQ);
668
669         return this;
670     }
671
672 }