OSDN Git Service

4bbf4ad68f82492ae5b8c8915c605ce7ec53e61f
[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:    return putRawCh(ch);
383         }
384
385         putRawText(escTxt);
386
387         return this;
388     }
389
390     /**
391      * {@inheritDoc}
392      * @param content {@inheritDoc}
393      * @return {@inheritDoc}
394      * @throws IOException {@inheritDoc}
395      */
396     @Override
397     public XmlExporter putContent(CharSequence content)
398             throws IOException{
399         int length = content.length();
400
401         char prev = '\0';
402         for(int pos = 0; pos < length; pos++){
403             char ch = content.charAt(pos);
404
405             if( isBasicLatinOnlyOut() && ! isBasicLatin(ch) ){
406                 putCharRef4Hex(ch);
407             }else if(ch == CH_YEN){
408                 putRawCh(CH_BSLASH);
409             }else if(Character.isSpaceChar(ch)){
410                 if(ch == CH_SP && prev != CH_SP){
411                     putRawCh(ch);
412                 }else{
413                     putCharRef2Hex(ch);
414                 }
415             }else{
416                 putCh(ch);
417             }
418
419             prev = ch;
420         }
421
422         return this;
423     }
424
425     /**
426      * {@inheritDoc}
427      * @param comment {@inheritDoc}
428      * @return {@inheritDoc}
429      * @throws IOException {@inheritDoc}
430      */
431     @Override
432     public XmlExporter putCommentContent(CharSequence comment)
433             throws IOException{
434         int length = comment.length();
435
436         char prev = '\0';
437         for(int pos = 0; pos < length; pos++){
438             char ch = comment.charAt(pos);
439
440             if(ch == '\n'){
441                 ln();
442             }else if('\u0000' <= ch && ch <= '\u001f'){
443                 putRawCh((char)('\u2400' + ch));
444             }else if(ch == '\u007f'){
445                 putRawCh('\u2421');
446             }else if(prev == '-' && ch == '-'){
447                 sp().putRawCh(ch);
448             }else{
449                 putRawCh(ch);
450             }
451
452             prev = ch;
453         }
454
455         return this;
456     }
457
458     /**
459      * {@inheritDoc}
460      * @param comment {@inheritDoc}
461      * @return {@inheritDoc}
462      * @throws IOException {@inheritDoc}
463      */
464     @Override
465     public XmlExporter putLineComment(CharSequence comment)
466             throws IOException{
467         putRawText(COMM_START).sp();
468         putCommentContent(comment);
469         sp().putRawText(COMM_END);
470         return this;
471     }
472
473     /**
474      * {@inheritDoc}
475      * @param comment {@inheritDoc}
476      * @return {@inheritDoc}
477      * @throws IOException {@inheritDoc}
478      */
479     @Override
480     public XmlExporter putBlockComment(CharSequence comment)
481             throws IOException{
482         putRawText(COMM_START).ln();
483
484         putCommentContent(comment);
485
486         int commentLength = comment.length();
487         if(commentLength > 0){
488             char lastCh = comment.charAt(commentLength - 1);
489             if(lastCh != '\n'){
490                 ln();
491             }
492         }
493
494         putRawText(COMM_END).ln();
495
496         return this;
497     }
498
499     /**
500      * {@inheritDoc}
501      * @param tagName {@inheritDoc}
502      * @return {@inheritDoc}
503      * @throws IOException {@inheritDoc}
504      */
505     @Override
506     public XmlExporter putOpenSTag(CharSequence tagName)
507             throws IOException{
508         putRawCh(CH_LT);
509         putRawText(tagName);
510         return this;
511     }
512
513     /**
514      * {@inheritDoc}
515      * @return {@inheritDoc}
516      * @throws IOException {@inheritDoc}
517      */
518     @Override
519     public XmlExporter putCloseSTag()
520             throws IOException{
521         putRawCh(CH_GT);
522         return this;
523     }
524
525     /**
526      * {@inheritDoc}
527      * @param tagName {@inheritDoc}
528      * @return {@inheritDoc}
529      * @throws IOException {@inheritDoc}
530      */
531     @Override
532     public XmlExporter putSimpleSTag(CharSequence tagName)
533             throws IOException{
534         putRawCh(CH_LT);
535         putRawText(tagName);
536         putRawCh(CH_GT);
537         return this;
538     }
539
540     /**
541      * {@inheritDoc}
542      * @param tagName {@inheritDoc}
543      * @return {@inheritDoc}
544      * @throws IOException {@inheritDoc}
545      */
546     @Override
547     public XmlExporter putETag(CharSequence tagName)
548             throws IOException{
549         putRawText("</");
550         putRawText(tagName);
551         putRawCh(CH_GT);
552         return this;
553     }
554
555     /**
556      * {@inheritDoc}
557      * @param tagName {@inheritDoc}
558      * @return {@inheritDoc}
559      * @throws IOException {@inheritDoc}
560      */
561     @Override
562     public XmlExporter putSimpleEmpty(CharSequence tagName)
563             throws IOException{
564         putRawCh(CH_LT);
565         putRawText(tagName).sp();
566         putCloseEmpty();
567         return this;
568     }
569
570     /**
571      * {@inheritDoc}
572      * @return {@inheritDoc}
573      * @throws IOException {@inheritDoc}
574      */
575     @Override
576     public XmlExporter putCloseEmpty()
577             throws IOException{
578         putRawText("/>");
579         return this;
580     }
581
582     /**
583      * {@inheritDoc}
584      * @param iVal {@inheritDoc}
585      * @return {@inheritDoc}
586      * @throws IOException {@inheritDoc}
587      */
588     @Override
589     public XmlExporter putXsdInt(int iVal) throws IOException{
590         String value = DatatypeIo.printInt(iVal);
591         putRawText(value);
592         return this;
593     }
594
595     /**
596      * {@inheritDoc}
597      * @param fVal {@inheritDoc}
598      * @return {@inheritDoc}
599      * @throws IOException {@inheritDoc}
600      */
601     @Override
602     public XmlExporter putXsdFloat(float fVal) throws IOException{
603         String value = DatatypeIo.printFloat(fVal);
604         putRawText(value);
605         return this;
606     }
607
608     /**
609      * {@inheritDoc}
610      * @param attrName {@inheritDoc}
611      * @param iVal {@inheritDoc}
612      * @return {@inheritDoc}
613      * @throws IOException {@inheritDoc}
614      */
615     @Override
616     public XmlExporter putIntAttr(CharSequence attrName,
617                                         int iVal)
618             throws IOException{
619         putRawText(attrName).putRawCh(CH_EQ);
620
621         putRawCh(CH_DQ);
622         putXsdInt(iVal);
623         putRawCh(CH_DQ);
624
625         return this;
626     }
627
628     /**
629      * {@inheritDoc}
630      * @param attrName {@inheritDoc}
631      * @param fVal {@inheritDoc}
632      * @return {@inheritDoc}
633      * @throws IOException {@inheritDoc}
634      */
635     @Override
636     public XmlExporter putFloatAttr(CharSequence attrName,
637                                            float fVal)
638             throws IOException{
639         putRawText(attrName).putRawCh(CH_EQ);
640
641         putRawCh(CH_DQ);
642         putXsdFloat(fVal);
643         putRawCh(CH_DQ);
644
645         return this;
646     }
647
648     /**
649      * {@inheritDoc}
650      * @param attrName {@inheritDoc}
651      * @param content {@inheritDoc}
652      * @return {@inheritDoc}
653      * @throws IOException {@inheritDoc}
654      */
655     @Override
656     public XmlExporter putAttr(CharSequence attrName,
657                                      CharSequence content)
658             throws IOException{
659         putRawText(attrName).putRawCh(CH_EQ);
660
661         putRawCh(CH_DQ);
662         putContent(content);
663         putRawCh(CH_DQ);
664
665         return this;
666     }
667
668 }