2 * abstract xml exporter
4 * License : The MIT License
5 * Copyright(c) 2013 MikuToga Partners
8 package jp.sfjp.mikutoga.xml;
10 import java.io.IOException;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13 import javax.xml.bind.DatatypeConverter;
16 * Appendable実装に依存したXMLエクスポータの半実装。
19 abstract class AbstractXmlExporter implements XmlExporter{
22 private static final String DEF_NL = "\n"; // 0x0a(LF)
24 private static final String DEF_INDENT_UNIT = "\u0020\u0020"; // ␣␣
26 private static final char CH_SP = '\u0020'; // ␣
27 private static final char CH_YEN = '\u00a5'; // ¥
28 private static final char CH_BSLASH = (char)0x005c; // \
29 private static final char CH_DQ = '\u0022'; // "
30 private static final char CH_SQ = (char)0x0027; // '
31 private static final char CH_EQ = '='; // =
32 private static final char CH_LT = '<';
33 private static final char CH_GT = '>';
35 private static final String COMM_START = "<!--";
36 private static final String COMM_END = "-->";
38 private static final Pattern NUM_FUZZY =
39 Pattern.compile("([^.]*\\.[0-9][0-9]*?)0+");
41 private static final String REF_HEX = "&#x";
42 private static final int HEX_EXP = 4; // 2 ** 4 == 16
43 private static final int MASK_1HEX = (1 << HEX_EXP) - 1; // 0b00001111
44 private static final int MAX_OCTET = (1 << Byte.SIZE) - 1; // 0xff
45 private static final char[] HEXCHAR_TABLE = {
46 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
47 'A', 'B', 'C', 'D', 'E', 'F',
51 assert HEX_EXP * 2 == Byte.SIZE;
52 assert HEXCHAR_TABLE.length == (1 << HEX_EXP);
56 private boolean basicLatinOnlyOut = true;
57 private String newline = DEF_NL;
58 private String indentUnit = DEF_INDENT_UNIT;
59 private int indentNest = 0;
65 protected AbstractXmlExporter(){
72 * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。
73 * <p>※ Basic-Latinには各種制御文字も含まれる。
75 * @return Basic-Latin文字ならtrue
76 * <a href="http://www.unicode.org/charts/PDF/U0000.pdf">
77 * Unicode 6.2 Controls and Basic Latin
80 protected static boolean isBasicLatin(char ch){
81 if('\u0000' <= ch && ch <= '\u007f'){
89 * <p>DatatypeConverterにおけるJDK1.6系と1.7系の仕様変更を吸収する。
90 * <p>0.001fは"0.0010"ではなく"0.001"と出力される。
93 * @return 冗長桁が抑止された実数表記
94 * @see javax.xml.bind.DatatypeConverter
96 protected static String chopFuzzyZero(String numTxt){
99 Matcher matcher = NUM_FUZZY.matcher(numTxt);
100 if(matcher.matches()){
101 result = matcher.group(1);
112 * @param ch {@inheritDoc}
113 * @return {@inheritDoc}
114 * @throws IOException {@inheritDoc}
117 public abstract Appendable append(char ch) throws IOException;
121 * @param seq {@inheritDoc}
122 * @return {@inheritDoc}
123 * @throws IOException {@inheritDoc}
126 public abstract Appendable append(CharSequence seq) throws IOException;
130 * @param seq {@inheritDoc}
131 * @param start {@inheritDoc}
132 * @param end {@inheritDoc}
133 * @return {@inheritDoc}
134 * @throws IOException {@inheritDoc}
137 public abstract Appendable append(CharSequence seq, int start, int end)
142 * @throws IOException {@inheritDoc}
145 public abstract void flush() throws IOException;
149 * @throws IOException {@inheritDoc}
152 public abstract void close() throws IOException;
157 * @param ch {@inheritDoc}
158 * @return {@inheritDoc}
159 * @throws IOException {@inheritDoc}
162 public XmlExporter putRawCh(char ch) throws IOException{
169 * @param seq {@inheritDoc}
170 * @return {@inheritDoc}
171 * @throws IOException {@inheritDoc}
174 public XmlExporter putRawText(CharSequence seq)
182 * @return {@inheritDoc}
183 * @throws IOException {@inheritDoc}
186 public XmlExporter sp() throws IOException{
193 * @param count {@inheritDoc}
194 * @return {@inheritDoc}
195 * @throws IOException {@inheritDoc}
198 public XmlExporter sp(int count) throws IOException{
199 for(int ct = 1; ct <= count; ct++){
207 * @return {@inheritDoc}
210 public String getNewLine(){
216 * @param newLine {@inheritDoc}
217 * @throws NullPointerException {@inheritDoc}
220 public void setNewLine(String newLine) throws NullPointerException{
221 if(newLine == null) throw new NullPointerException();
222 this.newline = newLine;
228 * @return {@inheritDoc}
229 * @throws IOException {@inheritDoc}
232 public XmlExporter ln() throws IOException{
233 putRawText(getNewLine());
239 * @param count {@inheritDoc}
240 * @return {@inheritDoc}
241 * @throws IOException {@inheritDoc}
244 public XmlExporter ln(int count) throws IOException{
245 for(int ct = 1; ct <= count; ct++){
253 * @return {@inheritDoc}
256 public String getIndentUnit(){
257 return this.indentUnit;
262 * @param indUnit {@inheritDoc}
263 * @throws NullPointerException {@inheritDoc}
266 public void setIndentUnit(String indUnit) throws NullPointerException{
267 if(indUnit == null) throw new NullPointerException();
268 this.indentUnit = indUnit;
276 public void pushNest(){
285 public void popNest(){
287 if(this.indentNest < 0) this.indentNest = 0;
293 * @return {@inheritDoc}
296 public int getIndentLevel(){
297 return this.indentNest;
302 * @return {@inheritDoc}
303 * @throws IOException {@inheritDoc}
306 public XmlExporter ind() throws IOException{
307 int level = getIndentLevel();
308 for(int ct = 1; ct <= level; ct++){
309 putRawText(getIndentUnit());
316 * @return {@inheritDoc}
319 public boolean isBasicLatinOnlyOut(){
320 return this.basicLatinOnlyOut;
325 * @param bool {@inheritDoc}
328 public void setBasicLatinOnlyOut(boolean bool){
329 this.basicLatinOnlyOut = bool;
335 * @param ch {@inheritDoc}
336 * @return {@inheritDoc}
337 * @throws IOException {@inheritDoc}
340 public XmlExporter putCharRef2Hex(char ch) throws IOException{
341 if(ch > MAX_OCTET) return putCharRef4Hex(ch);
343 int ibits = ch; // 常に正なので符号拡張なし
345 int idx4 = ibits & MASK_1HEX;
347 int idx3 = ibits & MASK_1HEX;
349 char hex3 = HEXCHAR_TABLE[idx3];
350 char hex4 = HEXCHAR_TABLE[idx4];
352 putRawText(REF_HEX).putRawCh(hex3).putRawCh(hex4)
360 * @param ch {@inheritDoc}
361 * @return {@inheritDoc}
362 * @throws IOException {@inheritDoc}
365 public XmlExporter putCharRef4Hex(char ch) throws IOException{
366 int ibits = ch; // 常に正なので符号拡張なし
368 int idx4 = ibits & MASK_1HEX;
370 int idx3 = ibits & MASK_1HEX;
372 int idx2 = ibits & MASK_1HEX;
374 int idx1 = ibits & MASK_1HEX;
376 char hex1 = HEXCHAR_TABLE[idx1];
377 char hex2 = HEXCHAR_TABLE[idx2];
378 char hex3 = HEXCHAR_TABLE[idx3];
379 char hex4 = HEXCHAR_TABLE[idx4];
381 putRawText(REF_HEX).putRawCh(hex1).putRawCh(hex2)
382 .putRawCh(hex3).putRawCh(hex4)
390 * @param ch {@inheritDoc}
391 * @return {@inheritDoc}
392 * @throws IOException {@inheritDoc}
395 public XmlExporter putCh(char ch) throws IOException{
396 if(Character.isISOControl(ch)){
403 case '&': escTxt = "&"; break;
404 case CH_LT: escTxt = "<"; break;
405 case CH_GT: escTxt = ">"; break;
406 case CH_DQ: escTxt = """; break;
407 case CH_SQ: escTxt = "'"; break;
408 default: escTxt = null; break;
422 * @param content {@inheritDoc}
423 * @return {@inheritDoc}
424 * @throws IOException {@inheritDoc}
427 public XmlExporter putContent(CharSequence content)
429 int length = content.length();
432 for(int pos = 0; pos < length; pos++){
433 char ch = content.charAt(pos);
435 if( isBasicLatinOnlyOut() && ! isBasicLatin(ch) ){
437 }else if(ch == CH_YEN){
439 }else if(Character.isSpaceChar(ch)){
440 if(ch == CH_SP && prev != CH_SP){
457 * @param comment {@inheritDoc}
458 * @return {@inheritDoc}
459 * @throws IOException {@inheritDoc}
462 public XmlExporter putCommentContent(CharSequence comment)
464 int length = comment.length();
467 for(int pos = 0; pos < length; pos++){
468 char ch = comment.charAt(pos);
472 }else if('\u0000' <= ch && ch <= '\u001f'){
473 putRawCh((char)('\u2400' + ch));
474 }else if(ch == '\u007f'){
476 }else if(prev == '-' && ch == '-'){
490 * @param comment {@inheritDoc}
491 * @return {@inheritDoc}
492 * @throws IOException {@inheritDoc}
495 public XmlExporter putLineComment(CharSequence comment)
497 putRawText(COMM_START).sp();
498 putCommentContent(comment);
499 sp().putRawText(COMM_END);
505 * @param comment {@inheritDoc}
506 * @return {@inheritDoc}
507 * @throws IOException {@inheritDoc}
510 public XmlExporter putBlockComment(CharSequence comment)
512 putRawText(COMM_START).ln();
514 putCommentContent(comment);
516 int commentLength = comment.length();
517 if(commentLength > 0){
518 char lastCh = comment.charAt(commentLength - 1);
524 putRawText(COMM_END).ln();
531 * @param tagName {@inheritDoc}
532 * @return {@inheritDoc}
533 * @throws IOException {@inheritDoc}
536 public XmlExporter putOpenSTag(CharSequence tagName)
545 * @return {@inheritDoc}
546 * @throws IOException {@inheritDoc}
549 public XmlExporter putCloseSTag()
557 * @param tagName {@inheritDoc}
558 * @return {@inheritDoc}
559 * @throws IOException {@inheritDoc}
562 public XmlExporter putSimpleSTag(CharSequence tagName)
572 * @param tagName {@inheritDoc}
573 * @return {@inheritDoc}
574 * @throws IOException {@inheritDoc}
577 public XmlExporter putETag(CharSequence tagName)
587 * @param tagName {@inheritDoc}
588 * @return {@inheritDoc}
589 * @throws IOException {@inheritDoc}
592 public XmlExporter putSimpleEmpty(CharSequence tagName)
595 putRawText(tagName).sp();
602 * @return {@inheritDoc}
603 * @throws IOException {@inheritDoc}
606 public XmlExporter putCloseEmpty()
614 * @param iVal {@inheritDoc}
615 * @return {@inheritDoc}
616 * @throws IOException {@inheritDoc}
619 public XmlExporter putXsdInt(int iVal) throws IOException{
620 String value = DatatypeConverter.printInt(iVal);
627 * @param fVal {@inheritDoc}
628 * @return {@inheritDoc}
629 * @throws IOException {@inheritDoc}
632 public XmlExporter putXsdFloat(float fVal) throws IOException{
633 String value = DatatypeConverter.printFloat(fVal);
634 value = chopFuzzyZero(value);
641 * @param attrName {@inheritDoc}
642 * @param iVal {@inheritDoc}
643 * @return {@inheritDoc}
644 * @throws IOException {@inheritDoc}
647 public XmlExporter putIntAttr(CharSequence attrName,
650 putRawText(attrName).putRawCh(CH_EQ);
661 * @param attrName {@inheritDoc}
662 * @param fVal {@inheritDoc}
663 * @return {@inheritDoc}
664 * @throws IOException {@inheritDoc}
667 public XmlExporter putFloatAttr(CharSequence attrName,
670 putRawText(attrName).putRawCh(CH_EQ);
681 * @param attrName {@inheritDoc}
682 * @param content {@inheritDoc}
683 * @return {@inheritDoc}
684 * @throws IOException {@inheritDoc}
687 public XmlExporter putAttr(CharSequence attrName,
688 CharSequence content)
690 putRawText(attrName).putRawCh(CH_EQ);