OSDN Git Service

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