OSDN Git Service

XML出力実体のインスタンス化
authorOlyutorskii <olyutorskii@users.osdn.me>
Sun, 26 Jun 2016 16:10:43 +0000 (01:10 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Sun, 26 Jun 2016 16:10:43 +0000 (01:10 +0900)
13 files changed:
src/main/java/jp/sourceforge/jindolf/archiver/AvatarData.java
src/main/java/jp/sourceforge/jindolf/archiver/DumpXmlTask.java
src/main/java/jp/sourceforge/jindolf/archiver/EventData.java
src/main/java/jp/sourceforge/jindolf/archiver/PeriodData.java
src/main/java/jp/sourceforge/jindolf/archiver/PeriodResource.java
src/main/java/jp/sourceforge/jindolf/archiver/SnifWriter.java
src/main/java/jp/sourceforge/jindolf/archiver/TalkData.java
src/main/java/jp/sourceforge/jindolf/archiver/TopicData.java
src/main/java/jp/sourceforge/jindolf/archiver/VillageData.java
src/main/java/jp/sourceforge/jindolf/archiver/XmlOut.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/XmlUtils.java
src/test/java/jp/sourceforge/jindolf/archiver/XmlOutTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jindolf/archiver/XmlUtilsTest.java [new file with mode: 0644]

index 652d9d8..6c2054e 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sourceforge.jindolf.archiver;
 
 import java.io.IOException;
-import java.io.Writer;
 import java.util.List;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -157,29 +156,30 @@ public class AvatarData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpXml(Writer writer) throws IOException{
-        writer.append("<avatar\n");
+    public void dumpXml(XmlOut writer) throws IOException{
+        writer.append("<avatar");
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "avatarId", this.avatarId);
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("avatarId", this.avatarId);
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "fullName", this.fullName);
+        writer.indent(1);
+        writer.attrOut("fullName", this.fullName);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "shortName", this.shortName);
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("shortName", this.shortName);
+        writer.nl();
 
         if(this.faceIconUri != null){
-            XmlUtils.indent(writer, 1);
-            XmlUtils.attrOut(writer, "faceIconURI", this.faceIconUri);
-            writer.append('\n');
+            writer.indent(1);
+            writer.attrOut("faceIconURI", this.faceIconUri);
+            writer.nl();
             // F1014対策
         }
 
-        writer.append("/>\n");
-        writer.flush();
+        writer.append("/>");
+        writer.nl();
 
         return;
     }
index f691213..a5c59d6 100644 (file)
@@ -21,7 +21,7 @@ public class DumpXmlTask implements Callable<Void> {
 
 
     private final VillageData villageData;
-    private final Writer writer;
+    private final XmlOut writer;
 
 
     /**
@@ -32,7 +32,7 @@ public class DumpXmlTask implements Callable<Void> {
     public DumpXmlTask(VillageData villageData, Writer writer){
         super();
         this.villageData = villageData;
-        this.writer = writer;
+        this.writer = new XmlOut(writer);
         return;
     }
 
@@ -58,7 +58,7 @@ public class DumpXmlTask implements Callable<Void> {
     @Override
     public Void call() throws IOException{
         try{
-            XmlUtils.dumpVillageData(this.writer, this.villageData);
+            this.writer.dumpVillageData(this.villageData);
         }finally{
             this.writer.close();
         }
index c5835ee..eef872f 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sourceforge.jindolf.archiver;
 
 import java.io.IOException;
-import java.io.Writer;
 import java.util.LinkedList;
 import java.util.List;
 import jp.sourceforge.jindolf.corelib.GameRole;
@@ -111,12 +110,13 @@ public class EventData extends TopicData{
      * @param avatar Avatar
      * @throws IOException 出力エラー
      */
-    public static void dumpAvatarRef(Writer writer, AvatarData avatar)
+    public static void dumpAvatarRef(XmlOut writer, AvatarData avatar)
             throws IOException{
         writer.append("<avatarRef");
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "avatarId", avatar.getAvatarId());
-        writer.append(" />\n");
+        writer.sp();
+        writer.attrOut("avatarId", avatar.getAvatarId());
+        writer.append(" />");
+        writer.nl();
         return;
     }
 
@@ -178,14 +178,14 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpOnstageAttr(Writer writer) throws IOException{
+    public void dumpOnstageAttr(XmlOut writer) throws IOException{
         int entryNo = this.intList.get(0);
         AvatarData avatarData = this.avatarList.get(0);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "entryNo", Integer.toString(entryNo));
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "avatarId", avatarData.getAvatarId());
+        writer.sp();
+        writer.attrOut("entryNo", Integer.toString(entryNo));
+        writer.sp();
+        writer.attrOut("avatarId", avatarData.getAvatarId());
 
         return;
     }
@@ -195,11 +195,11 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpSingleAvatarAttr(Writer writer) throws IOException{
+    public void dumpSingleAvatarAttr(XmlOut writer) throws IOException{
         AvatarData avatarData = this.avatarList.get(0);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "avatarId", avatarData.getAvatarId());
+        writer.sp();
+        writer.attrOut("avatarId", avatarData.getAvatarId());
 
         return;
     }
@@ -209,12 +209,12 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpCountingAttr(Writer writer) throws IOException{
+    public void dumpCountingAttr(XmlOut writer) throws IOException{
         int total = this.avatarList.size();
         if(total % 2 != 0){
             AvatarData victim = this.avatarList.get(total - 1);
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "victim", victim.getAvatarId());
+            writer.sp();
+            writer.attrOut("victim", victim.getAvatarId());
         }
         return;
     }
@@ -224,13 +224,13 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpExecutionAttr(Writer writer) throws IOException{
+    public void dumpExecutionAttr(XmlOut writer) throws IOException{
         int totalAvatar = this.avatarList.size();
         int totalVotes = this.intList.size();
         if(totalAvatar != totalVotes){
             AvatarData victim = this.avatarList.get(totalAvatar - 1);
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "victim", victim.getAvatarId());
+            writer.sp();
+            writer.attrOut("victim", victim.getAvatarId());
         }
         return;
     }
@@ -240,20 +240,20 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpAskEntryAttr(Writer writer) throws IOException{
+    public void dumpAskEntryAttr(XmlOut writer) throws IOException{
         int hour     = this.intList.get(0);
         int minute   = this.intList.get(1);
         int minLimit = this.intList.get(2);
         int maxLimit = this.intList.get(3);
 
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer, "commitTime", hour, minute);
+        writer.sp();
+        writer.timeAttrOut("commitTime", hour, minute);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "minMembers", Integer.toString(minLimit));
+        writer.sp();
+        writer.attrOut("minMembers", Integer.toString(minLimit));
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "maxMembers", Integer.toString(maxLimit));
+        writer.sp();
+        writer.attrOut("maxMembers", Integer.toString(maxLimit));
 
         return;
     }
@@ -263,15 +263,15 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpAskCommitAttr(Writer writer) throws IOException{
+    public void dumpAskCommitAttr(XmlOut writer) throws IOException{
         int hour     = this.intList.get(0);
         int minute   = this.intList.get(1);
 
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer, "limitVote", hour, minute);
+        writer.sp();
+        writer.timeAttrOut("limitVote", hour, minute);
 
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer, "limitSpecial", hour, minute);
+        writer.sp();
+        writer.timeAttrOut("limitSpecial", hour, minute);
 
         return;
     }
@@ -281,7 +281,7 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpStayEpilogueAttr(Writer writer) throws IOException{
+    public void dumpStayEpilogueAttr(XmlOut writer) throws IOException{
         GameRole role = this.roleList.get(0);
         int hour   = this.intList.get(0);
         int minute = this.intList.get(1);
@@ -293,11 +293,11 @@ public class EventData extends TopicData{
         case HAMSTER:  winner = "hamster"; break;
         default: throw new IllegalArgumentException();
         }
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "maxMembers", winner);
+        writer.sp();
+        writer.attrOut("maxMembers", winner);
 
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer, "limitTime", hour, minute);
+        writer.sp();
+        writer.timeAttrOut("limitTime", hour, minute);
 
         return;
     }
@@ -307,7 +307,7 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpOpenroleElem(Writer writer) throws IOException{
+    public void dumpOpenroleElem(XmlOut writer) throws IOException{
         int num = this.roleList.size();
         for(int index = 0; index < num; index++){
             int heads = this.intList.get(index);
@@ -315,11 +315,12 @@ public class EventData extends TopicData{
             String roleName = getRoleAttrValue(role);
 
             writer.append("<roleHeads");
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "role", roleName);
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "heads", Integer.toString(heads));
-            writer.append(" />\n");
+            writer.sp();
+            writer.attrOut("role", roleName);
+            writer.sp();
+            writer.attrOut("heads", Integer.toString(heads));
+            writer.append(" />");
+            writer.nl();
         }
         return;
     }
@@ -329,7 +330,7 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpMurderedElem(Writer writer) throws IOException{
+    public void dumpMurderedElem(XmlOut writer) throws IOException{
         for(AvatarData avatar : this.avatarList){
             dumpAvatarRef(writer, avatar);
         }
@@ -341,7 +342,7 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpSurvivorElem(Writer writer) throws IOException{
+    public void dumpSurvivorElem(XmlOut writer) throws IOException{
         for(AvatarData avatar : this.avatarList){
             dumpAvatarRef(writer, avatar);
         }
@@ -353,7 +354,7 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpNoCommentElem(Writer writer) throws IOException{
+    public void dumpNoCommentElem(XmlOut writer) throws IOException{
         for(AvatarData avatar : this.avatarList){
             dumpAvatarRef(writer, avatar);
         }
@@ -365,18 +366,19 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpCountingElem(Writer writer) throws IOException{
+    public void dumpCountingElem(XmlOut writer) throws IOException{
         int total = this.avatarList.size();
         total = total / 2 * 2;
         for(int index = 0; index < total; index += 2){
             AvatarData voteBy = this.avatarList.get(index);
             AvatarData voteTo = this.avatarList.get(index + 1);
             writer.append("<vote");
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "byWhom", voteBy.getAvatarId());
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "target", voteTo.getAvatarId());
-            writer.append(" />\n");
+            writer.sp();
+            writer.attrOut("byWhom", voteBy.getAvatarId());
+            writer.sp();
+            writer.attrOut("target", voteTo.getAvatarId());
+            writer.append(" />");
+            writer.nl();
         }
         return;
     }
@@ -386,17 +388,18 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpExecutionElem(Writer writer) throws IOException{
+    public void dumpExecutionElem(XmlOut writer) throws IOException{
         int total = this.intList.size();
         for(int index = 0; index < total; index++){
             AvatarData voteTo = this.avatarList.get(index);
             int count = this.intList.get(index);
             writer.append("<nominated");
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "avatarId", voteTo.getAvatarId());
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "count", "" + count);
-            writer.append(" />\n");
+            writer.sp();
+            writer.attrOut("avatarId", voteTo.getAvatarId());
+            writer.sp();
+            writer.attrOut("count", "" + count);
+            writer.append(" />");
+            writer.nl();
         }
         return;
     }
@@ -406,7 +409,7 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpPlayerlistElem(Writer writer) throws IOException{
+    public void dumpPlayerlistElem(XmlOut writer) throws IOException{
         int num = this.avatarList.size();
 
         for(int index = 0; index < num; index++){
@@ -421,25 +424,26 @@ public class EventData extends TopicData{
             String roleName = getRoleAttrValue(role);
 
             writer.append("<playerInfo");
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "playerId", account.toString());
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "avatarId", avatar.getAvatarId());
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "survive", survive);
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "role", roleName);
+            writer.sp();
+            writer.attrOut("playerId", account.toString());
+            writer.sp();
+            writer.attrOut("avatarId", avatar.getAvatarId());
+            writer.sp();
+            writer.attrOut("survive", survive);
+            writer.sp();
+            writer.attrOut("role", roleName);
 
             String uriStr = uri.toString();
             uriStr = uriStr.replaceAll("^[\\s]+", "");
             uriStr = uriStr.replaceAll("[\\s]+$", "");
             uriStr = uriStr.replaceAll("[\\s]+", "\u0020");
             if(uriStr.length() > 0){
-                writer.append(' ');
-                XmlUtils.attrOut(writer, "uri", uriStr);
+                writer.sp();
+                writer.attrOut("uri", uriStr);
             }
 
-            writer.append(" />\n");
+            writer.append(" />");
+            writer.nl();
         }
 
         return;
@@ -450,14 +454,14 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpByWhomAttr(Writer writer) throws IOException{
+    public void dumpByWhomAttr(XmlOut writer) throws IOException{
         AvatarData by = this.avatarList.get(0);
         AvatarData to = this.avatarList.get(1);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "byWhom", by.getAvatarId());
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "target", to.getAvatarId());
+        writer.sp();
+        writer.attrOut("byWhom", by.getAvatarId());
+        writer.sp();
+        writer.attrOut("target", to.getAvatarId());
 
         return;
     }
@@ -467,33 +471,33 @@ public class EventData extends TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpAssaultAttr(Writer writer) throws IOException{
+    public void dumpAssaultAttr(XmlOut writer) throws IOException{
         AvatarData by = this.avatarList.get(0);
         AvatarData to = this.avatarList.get(1);
 
-        writer.append('\n');
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "byWhom", by.getAvatarId());
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "target", to.getAvatarId());
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("byWhom", by.getAvatarId());
+        writer.sp();
+        writer.attrOut("target", to.getAvatarId());
+        writer.nl();
 
         DecodedContent xname = this.strList.get(0);
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "xname", xname);
+        writer.indent(1);
+        writer.attrOut("xname", xname);
 
         int hour = this.intList.get(0);
         int minute = this.intList.get(1);
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer, "time", hour, minute);
-        writer.append('\n');
+        writer.sp();
+        writer.timeAttrOut("time", hour, minute);
+        writer.nl();
 
         String icon = this.strList.get(1).toString();
         if( ! icon.equals(by.getFaceIconUri()) ){
-            XmlUtils.indent(writer, 1);
-            XmlUtils.attrOut(writer, "faceIconURI", icon);
-            writer.append('\n');
+            writer.indent(1);
+            writer.attrOut("faceIconURI", icon);
+            writer.nl();
         }
 
         return;
@@ -505,7 +509,7 @@ public class EventData extends TopicData{
      * @throws IOException 出力エラー
      */
     @Override
-    public void dumpXml(Writer writer) throws IOException{
+    public void dumpXml(XmlOut writer) throws IOException{
         String tagName = getTagName(this.eventType);
 
         writer.append("<");
@@ -548,8 +552,9 @@ public class EventData extends TopicData{
             break;
         }
 
-        if(hasAttr) writer.append(' ');
-        writer.append(">\n");
+        if(hasAttr) writer.sp();
+        writer.append(">");
+        writer.nl();
 
         dumpLines(writer);
 
@@ -582,7 +587,8 @@ public class EventData extends TopicData{
 
         writer.append("</");
         writer.append(tagName);
-        writer.append(">\n");
+        writer.append(">");
+        writer.nl();
 
         return;
     }
index a211d46..bdb2afd 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sourceforge.jindolf.archiver;
 
 import java.io.IOException;
-import java.io.Writer;
 import java.net.URI;
 import java.util.LinkedList;
 import java.util.List;
@@ -166,8 +165,8 @@ public class PeriodData{
         if(topicData instanceof EventData){
             EventData event = (EventData) topicData;
             SysEventType type = event.getEventType();
-            if(   type == SysEventType.MURDERED
-               || type == SysEventType.NOMURDER){
+            if(    type == SysEventType.MURDERED
+                || type == SysEventType.NOMURDER){
                 this.hasMurderResult = true;
             }
         }
@@ -180,8 +179,9 @@ public class PeriodData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpXml(Writer writer) throws IOException{
-        writer.append("<period\n");
+    public void dumpXml(XmlOut writer) throws IOException{
+        writer.append("<period");
+        writer.nl();
 
         String ptype;
         switch(this.resource.getPeriodType()){
@@ -198,58 +198,57 @@ public class PeriodData{
             throw new IllegalArgumentException();
         }
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "type", ptype);
+        writer.indent(1);
+        writer.attrOut("type", ptype);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer,
-                "day", Integer.toString(this.resource.getDay()));
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("day", Integer.toString(this.resource.getDay()));
+        writer.nl();
 
         if(this.disclosureType != DisclosureType.COMPLETE){
-            XmlUtils.indent(writer, 1);
-            XmlUtils.attrOut(writer,
-                    "disclosure", this.disclosureType.getXmlName());
-            writer.append('\n');
+            writer.indent(1);
+            writer.attrOut("disclosure", this.disclosureType.getXmlName());
+            writer.nl();
         }
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.dateAttrOut(writer, "nextCommitDay",
-                             this.commitMonth, this.commitDay);
+        writer.indent(1);
+        writer.dateAttrOut("nextCommitDay",
+                           this.commitMonth, this.commitDay);
 
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer,
-                             "commitTime",
-                             this.commitHour, this.commitMinute);
-        writer.append('\n');
+        writer.sp();
+        writer.timeAttrOut("commitTime",
+                           this.commitHour, this.commitMinute);
+        writer.nl();
 
         URI baseUri   = URI.create(this.parent.getBaseUri());
         URI periodUri = URI.create(this.resource.getOrigUrlText());
         URI relativeUri = baseUri.relativize(periodUri);
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "sourceURI", relativeUri.toString());
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("sourceURI", relativeUri.toString());
+        writer.nl();
 
         long downTimeMs = this.resource.getDownTimeMs();
-        XmlUtils.indent(writer, 1);
-        XmlUtils.dateTimeAttr(writer, "loadedTime", downTimeMs);
-        writer.append('\n');
+        writer.indent(1);
+        writer.dateTimeAttr("loadedTime", downTimeMs);
+        writer.nl();
 
         if(this.loginName.length() > 0){
-            XmlUtils.indent(writer, 1);
-            XmlUtils.attrOut(writer, "loadedBy", this.loginName.toString());
-            writer.append('\n');
+            writer.indent(1);
+            writer.attrOut("loadedBy", this.loginName.toString());
+            writer.nl();
         }
 
-        writer.append(">\n\n");
+        writer.append(">");
+        writer.nl();
+        writer.nl();
 
         for(TopicData topic : this.topicList){
             topic.dumpXml(writer);
-            writer.append('\n');
-            writer.flush();
+            writer.nl();
         }
 
-        writer.append("</period>\n");
+        writer.append("</period>");
+        writer.nl();
 
         return;
     }
index b6290cc..cfc0957 100644 (file)
@@ -59,7 +59,7 @@ public class PeriodResource{
      * @return 国情報
      */
     public LandDef getLandDef(){
-        return landDef;
+        return this.landDef;
     }
 
     /**
@@ -67,7 +67,7 @@ public class PeriodResource{
      * @return 村番号
      */
     public int getVillageId(){
-        return villageId;
+        return this.villageId;
     }
 
     /**
@@ -75,7 +75,7 @@ public class PeriodResource{
      * @return Period種別
      */
     public PeriodType getPeriodType(){
-        return periodType;
+        return this.periodType;
     }
 
     /**
@@ -83,7 +83,7 @@ public class PeriodResource{
      * @return 日付
      */
     public int getDay(){
-        return day;
+        return this.day;
     }
 
     /**
@@ -91,7 +91,7 @@ public class PeriodResource{
      * @return ダウンロード元URL文字列
      */
     public String getOrigUrlText(){
-        return origUrlText;
+        return this.origUrlText;
     }
 
     /**
@@ -116,7 +116,7 @@ public class PeriodResource{
      * @return 格納先URL
      */
     public URL getResourceUrl(){
-        return resourceUrl;
+        return this.resourceUrl;
     }
 
     /**
index 55214ba..a95b58c 100644 (file)
@@ -18,6 +18,9 @@ import java.io.Writer;
  */
 public class SnifWriter extends Writer{
 
+    private static final int SZ_PIPEREAD = 8 * 1024;
+
+
     private final Writer fout;
 
     private final Writer pout;
@@ -35,7 +38,7 @@ public class SnifWriter extends Writer{
         this.fout = writer;
 
         PipedWriter pipeOut = new PipedWriter();
-        PipedReader pipeIn  = new PipedReader();
+        PipedReader pipeIn  = new PipedReader(SZ_PIPEREAD);
 
         try{
             pipeOut.connect(pipeIn);
index 93a84cd..3bff0cb 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sourceforge.jindolf.archiver;
 
 import java.io.IOException;
-import java.io.Writer;
 import jp.sourceforge.jindolf.corelib.TalkType;
 
 /**
@@ -139,8 +138,9 @@ public class TalkData extends TopicData{
      * @throws IOException 出力エラー
      */
     @Override
-    public void dumpXml(Writer writer) throws IOException{
-        writer.append("<talk\n");
+    public void dumpXml(XmlOut writer) throws IOException{
+        writer.append("<talk");
+        writer.nl();
 
         String typeStr;
         switch(this.talkType){
@@ -160,32 +160,35 @@ public class TalkData extends TopicData{
             throw new IllegalArgumentException();
         }
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "type", typeStr);
+        writer.indent(1);
+        writer.attrOut("type", typeStr);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "avatarId", this.avatarData.getAvatarId());
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("avatarId", this.avatarData.getAvatarId());
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "xname", this.xName);
+        writer.indent(1);
+        writer.attrOut("xname", this.xName);
 
-        writer.append(' ');
-        XmlUtils.timeAttrOut(writer, "time", this.hour, this.minute);
-        writer.append('\n');
+        writer.sp();
+        writer.timeAttrOut("time", this.hour, this.minute);
+        writer.nl();
 
-        if(   this.talkType != TalkType.GRAVE
-           && ! this.faceIconUri.equals(this.avatarData.getFaceIconUri()) ){
-            XmlUtils.indent(writer, 1);
-            XmlUtils.attrOut(writer, "faceIconURI", this.faceIconUri);
-            writer.append('\n');
+        if(    this.talkType != TalkType.GRAVE
+            && ! this.faceIconUri.equals(this.avatarData.getFaceIconUri()) ){
+            writer.indent(1);
+            writer.attrOut("faceIconURI", this.faceIconUri);
+            writer.nl();
         }
 
-        writer.append(">\n");
+        writer.append(">");
+        writer.nl();
 
         dumpLines(writer);
 
-        writer.append("</talk>\n");
+        writer.append("</talk>");
+        writer.nl();
+
         return;
     }
 
index 1c988fc..aee22be 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sourceforge.jindolf.archiver;
 
 import java.io.IOException;
-import java.io.Writer;
 import java.util.LinkedList;
 import java.util.List;
 import jp.sourceforge.jindolf.parser.DecodedContent;
@@ -68,7 +67,7 @@ public abstract class TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpLines(Writer writer) throws IOException{
+    public void dumpLines(XmlOut writer) throws IOException{
         DecodedContent lastLine = null;
         DecodedContent lastContent = null;
 
@@ -76,25 +75,27 @@ public abstract class TopicData{
             lastContent = content;
             if(content == BREAK){
                 if(lastLine != null){
-                    writer.append("</li>\n");
+                    writer.append("</li>");
                     lastLine = null;
                 }else{
-                    writer.append("<li/>\n");
+                    writer.append("<li/>");
                 }
+                writer.nl();
             }else{
                 if(lastLine == null){
                     writer.append("<li>");
                 }
-                XmlUtils.dumpDecodedContent(writer, content);
+                writer.dumpDecodedContent(content);
                 lastLine = content;
             }
         }
 
         if(lastLine != null){
-            writer.append("</li>\n");
+            writer.append("</li>");
         }else if(lastContent == BREAK){
-            writer.append("<li/>\n");
+            writer.append("<li/>");
         }
+        writer.nl();
 
         return;
     }
@@ -104,6 +105,6 @@ public abstract class TopicData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public abstract void dumpXml(Writer writer) throws IOException;
+    public abstract void dumpXml(XmlOut writer) throws IOException;
 
 }
index fce44ab..9f6becf 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sourceforge.jindolf.archiver;
 
 import java.io.IOException;
-import java.io.Writer;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -345,15 +344,18 @@ public class VillageData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpAvatarList(Writer writer) throws IOException{
-        writer.append("<avatarList>").append("\n\n");
+    public void dumpAvatarList(XmlOut writer) throws IOException{
+        writer.append("<avatarList>");
+        writer.nl();
+        writer.nl();
 
         for(AvatarData avatar : this.avatarList){
             avatar.dumpXml(writer);
-            writer.append('\n');
+            writer.nl();
         }
 
-        writer.append("</avatarList>").append('\n');
+        writer.append("</avatarList>");
+        writer.nl();
 
         return;
     }
@@ -363,10 +365,10 @@ public class VillageData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpPeriodList(Writer writer) throws IOException{
+    public void dumpPeriodList(XmlOut writer) throws IOException{
         for(PeriodData period : this.periodList){
             period.dumpXml(writer);
-            writer.append('\n');
+            writer.nl();
         }
         return;
     }
@@ -376,49 +378,49 @@ public class VillageData{
      * @param writer 出力先
      * @throws IOException 出力エラー
      */
-    public void dumpXml(Writer writer) throws IOException{
-        writer.append("<village\n");
+    public void dumpXml(XmlOut writer) throws IOException{
+        writer.append("<village");
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.dumpNameSpaceDecl(writer);
-        writer.append('\n');
+        writer.indent(1);
+        writer.dumpNameSpaceDecl();
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.dumpSiNameSpaceDecl(writer);
-        writer.append('\n');
+        writer.indent(1);
+        writer.dumpSiNameSpaceDecl();
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.dumpSchemeLocation(writer);
-        writer.append('\n');
+        writer.indent(1);
+        writer.dumpSchemeLocation();
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "xml:lang", "ja-JP");
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("xml:lang", "ja-JP");
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "xml:base", this.baseUri);
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("xml:base", this.baseUri);
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "fullName", this.fullName);
+        writer.indent(1);
+        writer.attrOut("fullName", this.fullName);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "vid", Integer.toString(this.villageId));
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("vid", Integer.toString(this.villageId));
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.timeAttrOut(writer,
-                             "commitTime",
-                             this.commitHour, this.commitMinute);
-        writer.append('\n');
+        writer.indent(1);
+        writer.timeAttrOut("commitTime",
+                           this.commitHour, this.commitMinute);
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "state", "gameover");
+        writer.indent(1);
+        writer.attrOut("state", "gameover");
 
         DisclosureType type = getDisclosureType();
         if(type != DisclosureType.COMPLETE){
-            writer.append(' ');
-            XmlUtils.attrOut(writer, "disclosure", type.getXmlName());
+            writer.sp();
+            writer.attrOut("disclosure", type.getXmlName());
         }
 
         String isValid;
@@ -427,55 +429,55 @@ public class VillageData{
         }else{
             isValid = "false";
         }
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "isValid", isValid);
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("isValid", isValid);
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "landName", this.landDef.getLandName());
+        writer.indent(1);
+        writer.attrOut("landName", this.landDef.getLandName());
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "formalName", this.landDef.getFormalName());
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("formalName", this.landDef.getFormalName());
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "landId", this.landDef.getLandId());
+        writer.indent(1);
+        writer.attrOut("landId", this.landDef.getLandId());
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer, "landPrefix", this.landDef.getLandPrefix());
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("landPrefix", this.landDef.getLandPrefix());
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
+        writer.indent(1);
         String locale = this.landDef.getLocale().toString();
         locale = locale.replaceAll("_", "-");
-        XmlUtils.attrOut(writer, "locale", locale);
+        writer.attrOut("locale", locale);
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer,
-                "origencoding", this.landDef.getEncoding().name());
+        writer.sp();
+        writer.attrOut("origencoding", this.landDef.getEncoding().name());
 
-        writer.append(' ');
-        XmlUtils.attrOut(writer,
-                "timezone", this.landDef.getTimeZone().getID());
-        writer.append('\n');
+        writer.sp();
+        writer.attrOut("timezone", this.landDef.getTimeZone().getID());
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "graveIconURI", getGraveIconUri());
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("graveIconURI", getGraveIconUri());
+        writer.nl();
 
-        XmlUtils.indent(writer, 1);
-        XmlUtils.attrOut(writer, "generator", JinArchiver.GENERATOR);
-        writer.append('\n');
+        writer.indent(1);
+        writer.attrOut("generator", JinArchiver.GENERATOR);
+        writer.nl();
 
-        writer.append(">").append('\n');
+        writer.append(">");
+        writer.nl();
 
-        writer.append('\n');
+        writer.nl();
         dumpAvatarList(writer);
 
-        writer.append('\n');
+        writer.nl();
         dumpPeriodList(writer);
 
-        writer.append("</village>").append("\n");
+        writer.append("</village>");
+        writer.nl();
 
         return;
     }
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/XmlOut.java b/src/main/java/jp/sourceforge/jindolf/archiver/XmlOut.java
new file mode 100644 (file)
index 0000000..dee7607
--- /dev/null
@@ -0,0 +1,662 @@
+/*
+ * XML output
+ *
+ * License : The MIT License
+ * Copyright(c) 2016 olyutorskii
+ */
+
+package jp.sourceforge.jindolf.archiver;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
+import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
+import jp.sourceforge.jindolf.parser.DecodedContent;
+
+
+/**
+ * XML出力。
+ */
+public class XmlOut implements Appendable, Flushable, Closeable{
+
+    private static final String ORIG_DTD =
+            "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";
+    private static final String ORIG_NS =
+            "http://jindolf.sourceforge.jp/xml/ns/501";
+    private static final String ORIG_SCHEME =
+            "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";
+    private static final String SCHEMA_NS =
+            "http://www.w3.org/2001/XMLSchema-instance";
+
+    private static final char BS_CHAR = (char) 0x005c; // Backslash
+    private static final char SQ_CHAR = '\'';
+    private static final char DQ_CHAR = '"';
+    private static final char TILDE_CHAR     = '\u007e';
+    private static final char DELETE_CHAR    = '\u007f';
+    private static final char YEN_CHAR       = '\u00a5';
+    private static final char OVERLINE_CHAR  = '\u203e';
+    private static final char SYMNULL_CHAR   = '\u2400';
+    private static final char SYMDELETE_CHAR = '\u2421';
+    private static final char REP_CHAR       = '\ufffd';
+    private static final String INDENT_UNIT = "\u0020\u0020";
+
+    private static final String FORM_XSD_DATETIME =
+              "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
+            + "T{3,number,#00}:{4,number,#00}:{5,number,#00}"
+            + ".{6,number,#000}+09:00";
+
+    private static final TimeZone TZ_TOKYO =
+            TimeZone.getTimeZone("Asia/Tokyo");
+
+
+    private final Writer writer;
+
+
+    /**
+     * コンストラクタ。
+     * @param writer 出力
+     */
+    XmlOut(Writer writer){
+        super();
+        this.writer = writer;
+        return;
+    }
+
+
+    /**
+     * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
+     * @param chVal 文字
+     * @return ホワイトスペースならtrue
+     */
+    public static boolean isWhiteSpace(char chVal){
+        switch(chVal){
+        case '\u0020':
+        case '\t':
+        case '\n':
+        case '\r':
+            return true;
+        default:
+            break;
+        }
+
+        return false;
+    }
+
+    /**
+     * XMLに出現可能な文字(Char)か判定する。
+     *
+     * <p>サロゲートペアのシーケンスまでは調べない。
+     *
+     * @param chVal 文字
+     * @return 出現可能ならtrue
+     */
+    public static boolean isXmlChar(char chVal){
+        if('\u0020' <= chVal && chVal <= '\ud7ff') return true;
+        if('\ue000' <= chVal && chVal <= '\ufffd') return true;
+
+        if(chVal == '\t' || chVal == '\n' || chVal == '\r') return true;
+
+        if(Character.isSurrogate(chVal)) return true;
+
+        return false;
+    }
+
+    /**
+     * 印字可能な代替キャラクタへの変換を行う。
+     *
+     * <p>ControlPicturesが利用できない場合はU+FFFDを用いる。
+     *
+     * <p>UnicodeのControl Picturesブロックを参照せよ。
+     *
+     * @param chVal 対象文字
+     * @return 代替キャラクタ
+     */
+    public static char replaceChar(char chVal){
+        char result;
+
+        if('\u0000' <= chVal && chVal <= '\u001f'){
+            result = (char) ( chVal + SYMNULL_CHAR );
+        }else if(chVal == DELETE_CHAR){
+            result = SYMDELETE_CHAR;
+        }else{
+            result = REP_CHAR;
+        }
+
+        return result;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * @param csq {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public Appendable append(CharSequence csq) throws IOException{
+        this.writer.append(csq);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param csq {@inheritDoc}
+     * @param start {@inheritDoc}
+     * @param end {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public Appendable append(CharSequence csq, int start, int end)
+            throws IOException{
+        this.writer.append(csq, start, end);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param c {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public Appendable append(char c) throws IOException{
+        this.writer.append(c);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public void flush() throws IOException{
+        this.writer.flush();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public void close() throws IOException{
+        this.writer.close();
+        return;
+    }
+
+    /**
+     * 空白を出力する。
+     * @throws IOException 出力エラー
+     */
+    public void sp() throws IOException{
+        append('\u0020');
+        return;
+    }
+
+    /**
+     * 改行を出力する。
+     * @throws IOException 出力エラー
+     */
+    public void nl() throws IOException{
+        append('\n');
+        return;
+    }
+
+    /**
+     * インデント用空白を出力する。
+     * ネスト単位は空白2文字
+     * @param level ネストレベル
+     * @throws IOException 出力エラー
+     */
+    public void indent(int level) throws IOException{
+        for(int ct = 1; ct <= level; ct++){
+            append(INDENT_UNIT);
+        }
+        return;
+    }
+
+    /**
+     * XML数値文字参照を出力する。
+     * @param chVal 出力文字
+     * @throws IOException 出力エラー
+     */
+    public void charRefOut(char chVal)
+            throws IOException{
+        int ival = 0xffff & ((int) chVal);
+        String hex = Integer.toHexString(ival);
+        if(hex.length() % 2 != 0) hex = "0" + hex;
+
+        append("&#x");
+        append(hex);
+        append(";");
+
+        return;
+    }
+
+    /**
+     * 属性を出力する。
+     * @param name 属性名
+     * @param value 属性値
+     * @throws IOException 出力エラー
+     */
+    public void attrOut(CharSequence name, CharSequence value)
+            throws IOException{
+        append(name);
+
+        append('=');
+
+        append(DQ_CHAR);
+        attrValOut(value);
+        append(DQ_CHAR);
+
+        return;
+    }
+
+    /**
+     * 属性値を出力する。
+     *
+     * <p>XML規格のAttValueを参照せよ。
+     *
+     * @param value 属性値
+     * @throws IOException 出力エラー
+     */
+    private void attrValOut(CharSequence value) throws IOException{
+        int len = value.length();
+
+        for(int pos = 0; pos < len; pos++){
+            char chVal = value.charAt(pos);
+            switch (chVal){
+            case '&':
+                append("&amp;");
+                break;
+            case '<':
+                append("&lt;");
+                break;
+            case '>':
+                append("&gt;");
+                break;
+            case DQ_CHAR:
+                append("&quot;");
+                break;
+            case SQ_CHAR:
+                append("&apos;");
+                break;
+            default:
+                append(chVal);
+                break;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * xsd:time形式の時刻属性を出力する。
+     * タイムゾーンは「+09:00」固定
+     * @param name 属性名
+     * @param hour 時間
+     * @param minute 分
+     * @throws IOException 出力エラー
+     */
+    public void timeAttrOut(CharSequence name, int hour, int minute)
+            throws IOException{
+        append(name);
+
+        append('=');
+
+        append(DQ_CHAR);
+        digi2colOut(hour);
+        append(':');
+        digi2colOut(minute);
+        append(":00+09:00");
+        append(DQ_CHAR);
+
+        return;
+    }
+
+    /**
+     * xsd:gMonthDay形式の日付属性を出力する。
+     * タイムゾーンは「+09:00」固定
+     * @param name 属性名
+     * @param month 月
+     * @param day 日
+     * @throws IOException 出力エラー
+     */
+    public void dateAttrOut(CharSequence name, int month, int day)
+            throws IOException{
+        append(name);
+
+        append('=');
+
+        append(DQ_CHAR);
+        append("--");
+        digi2colOut(month);
+        append('-');
+        digi2colOut(day);
+        append("+09:00");
+        append(DQ_CHAR);
+
+        return;
+    }
+
+    /**
+     * 二桁の整数を出力する。
+     *
+     * <p>負の値の出力は未定義。
+     *
+     * <p>100より大きい値の出力は未定義。
+     *
+     * @param digit 整数
+     * @throws IOException 出力エラー
+     */
+    private void digi2colOut(int digit) throws IOException{
+        int col2 = Math.abs(digit) % 100;
+
+        char ch1st = (char) ('0' + (col2 / 10));
+        char ch2nd = (char) ('0' + (col2 % 10));
+
+        append(ch1st);
+        append(ch2nd);
+
+        return;
+    }
+
+    /**
+     * xsd:dateTime形式の日付時刻属性を出力する。
+     * タイムゾーンは「+09:00」固定
+     * @param name 属性名
+     * @param epochMs エポック時刻
+     * @throws IOException 出力エラー
+     */
+    public void dateTimeAttr(CharSequence name, long epochMs)
+            throws IOException{
+        Calendar calendar = new GregorianCalendar(TZ_TOKYO);
+
+        calendar.setTimeInMillis(epochMs);
+        int year = calendar.get(Calendar.YEAR);
+        int month = calendar.get(Calendar.MONTH) + 1;
+        int day = calendar.get(Calendar.DATE);
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int minute = calendar.get(Calendar.MINUTE);
+        int sec = calendar.get(Calendar.SECOND);
+        int msec = calendar.get(Calendar.MILLISECOND);
+
+        String attrVal =
+                MessageFormat.format(
+                        FORM_XSD_DATETIME,
+                        year, month, day, hour, minute, sec, msec
+                );
+
+        attrOut(name, attrVal);
+
+        return;
+    }
+
+    /**
+     * デコードエラー込みのテキストを出力する。
+     * @param content テキスト
+     * @throws IOException 出力エラー
+     */
+    public void dumpDecodedContent(DecodedContent content)
+            throws IOException{
+        if( ! content.hasDecodeError() ){
+            charDataOut(content);
+            return;
+        }
+
+        int last = 0;
+
+        List<DecodeErrorInfo> errList = content.getDecodeErrorList();
+        for(DecodeErrorInfo err : errList){
+            int charPos = err.getCharPosition();
+            CharSequence line = content.subSequence(last, charPos);
+            charDataOut(line);
+            dumpErrorInfo(err);
+            last = charPos + 1;
+        }
+
+        CharSequence line = content.subSequence(last, content.length());
+        charDataOut(line);
+
+        return;
+    }
+
+    /**
+     * デコードエラー情報をrawdataタグで出力する。
+     * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
+     * @param errorInfo デコードエラー
+     * @throws IOException 出力エラー
+     */
+    public void dumpErrorInfo(DecodeErrorInfo errorInfo)
+            throws IOException{
+        int hexVal;
+        hexVal = errorInfo.getRawByte1st() & 0xff;
+        if(errorInfo.has2nd()){
+            hexVal <<= 8;
+            hexVal |= errorInfo.getRawByte2nd() & 0xff;
+        }
+
+        String hexBin = Integer.toHexString(hexVal);
+        if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
+
+        char replaceChar = Win31j.getWin31jChar(errorInfo);
+
+        rawDataOut(hexBin, replaceChar);
+
+        return;
+    }
+
+    /**
+     * 生データ出力
+     * @param hex 16進文字列
+     * @param replace 代替キャラクタ
+     * @throws IOException 出力エラー
+     */
+    private void rawDataOut(String hex, char replace) throws IOException{
+        append("<rawdata");
+
+        sp();
+        attrOut("encoding", "Shift_JIS");
+
+        sp();
+        attrOut("hexBin", hex);
+
+        sp();
+        append(">");
+        append(replace);
+        append("</rawdata>");
+
+        return;
+    }
+
+    /**
+     * XMLのCharData文字列を出力する。
+     * <ul>
+     * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
+     * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
+     * <li>スペースでないホワイトスペースは無条件に文字参照化される。
+     * <li>{@literal &, <, >, "}は無条件に文字参照化される。
+     * </ul>
+     * 参考:XML 1.0 規格 3.3.3節
+     * @param seq CDATA文字列
+     * @throws IOException 出力エラー
+     */
+    public void charDataOut(CharSequence seq)
+            throws IOException{
+        int len = seq.length();
+
+        boolean leadSpace = false;
+
+        for(int pos = 0; pos < len; pos++){
+            char chVal = seq.charAt(pos);
+
+            if(isWhiteSpace(chVal)){
+                boolean is1stPos  = pos == 0;
+                boolean isLastPos = pos >= len - 1;
+                if(is1stPos || isLastPos || leadSpace){
+                    charRefOut(chVal);
+                }else if(chVal != '\u0020'){
+                    charRefOut(chVal);
+                }else{
+                    append(chVal);
+                }
+                leadSpace = true;
+            }else{
+                nonSpaceOut(chVal);
+                leadSpace = false;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * CharDataのホワイトスペース以外の文字を出力する。
+     * @param chVal 文字
+     * @throws IOException 出力エラー
+     */
+    private void nonSpaceOut(char chVal) throws IOException{
+        if(chVal == '&'){
+            append("&amp;");
+        }else if(chVal == '<'){
+            append("&lt;");
+        }else if(chVal == '>'){
+            append("&gt;");
+        }else if(chVal == DQ_CHAR){
+            append("&quot;");
+        }else if(chVal == SQ_CHAR){
+            append("&apos;");
+        }else if(chVal == BS_CHAR){
+            append(YEN_CHAR);
+        }else if(chVal == TILDE_CHAR){
+            append(OVERLINE_CHAR);
+        }else if(! isXmlChar(chVal)){
+            // TODO: U+007fの扱い
+            dumpRawData(chVal);
+        }else{
+            append(chVal);
+        }
+
+        return;
+    }
+
+    /**
+     * 不正文字をXML出力する。
+     * @param chVal 不正文字
+     * @throws IOException 出力エラー
+     */
+    public void dumpRawData(char chVal)
+            throws IOException{
+        int hexVal;
+        hexVal = chVal & 0xff;
+        String hexBin = Integer.toHexString(hexVal);
+        if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
+
+        char replaceChar = replaceChar(chVal);
+
+        rawDataOut(hexBin, replaceChar);
+
+        return;
+    }
+
+    /**
+     * 村情報をXML形式で出力する。
+     * @param villageData 村情報
+     * @throws IOException 出力エラー
+     */
+    public void dumpVillageData(VillageData villageData)
+            throws IOException{
+        append("<?xml");
+        sp();
+        attrOut("version", "1.0");
+        sp();
+        attrOut("encoding", "UTF-8");
+        sp();
+        append("?>");
+        nl();
+        nl();
+
+        append("<!--");
+        nl();
+
+        indent(1);
+        append("人狼BBSアーカイブ");
+        nl();
+
+        indent(1);
+        append("http://jindolf.sourceforge.jp/");
+        nl();
+
+        append("-->");
+        nl();
+        nl();
+
+        dumpDocType();
+        nl();
+        nl();
+
+        villageData.dumpXml(this);
+
+        nl();
+        append("<!-- EOF -->");
+        nl();
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * DOCTYPE宣言を出力する。
+     * @throws IOException 出力エラー
+     */
+    public void dumpDocType() throws IOException{
+        append("<!DOCTYPE village SYSTEM");
+        sp();
+        append('"');
+        append(ORIG_DTD);
+        append('"');
+        sp();
+        append(">");
+        return;
+    }
+
+    /**
+     * オリジナルNameSpace宣言を出力する。
+     * @throws IOException 出力エラー
+     */
+    public void dumpNameSpaceDecl()
+            throws IOException{
+        attrOut("xmlns", ORIG_NS);
+        return;
+    }
+
+    /**
+     * スキーマNameSpace宣言を出力する。
+     * @throws IOException 出力エラー
+     */
+    public void dumpSiNameSpaceDecl()
+            throws IOException{
+        attrOut("xmlns:xsi", SCHEMA_NS);
+        return;
+    }
+
+    /**
+     * スキーマ位置指定を出力する。
+     * @throws IOException 出力エラー
+     */
+    public void dumpSchemeLocation()
+            throws IOException{
+        attrOut("xsi:schemaLocation",
+                ORIG_NS + " " + ORIG_SCHEME);
+        return;
+    }
+
+}
index b4ada54..3ed6417 100644 (file)
@@ -7,13 +7,6 @@
 
 package jp.sourceforge.jindolf.archiver;
 
-import java.io.IOException;
-import java.io.Writer;
-import java.text.MessageFormat;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.TimeZone;
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -21,8 +14,6 @@ import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
-import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
-import jp.sourceforge.jindolf.parser.DecodedContent;
 import org.xml.sax.SAXException;
 
 /**
@@ -30,27 +21,12 @@ import org.xml.sax.SAXException;
  */
 public final class XmlUtils{
 
-    private static final String ORIG_DTD =
-            "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd";
-    private static final String ORIG_NS =
-            "http://jindolf.sourceforge.jp/xml/ns/501";
-    private static final String ORIG_SCHEME =
-            "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd";
-    private static final String SCHEMA_NS =
-            "http://www.w3.org/2001/XMLSchema-instance";
-
-    private static final char BS_CHAR = (char) 0x005c; // Backslash
-    private static final String INDENT_UNIT = "\u0020\u0020";
-
-    private static final TimeZone TZ_TOKYO =
-            TimeZone.getTimeZone("Asia/Tokyo");
-
-
     /**
      * 隠れコンストラクタ。
      */
     private XmlUtils(){
-        throw new Error();
+        assert false;
+        throw new AssertionError();
     }
 
 
@@ -88,408 +64,4 @@ public final class XmlUtils{
         return validator;
     }
 
-    /**
-     * DOCTYPE宣言を出力する。
-     * @param writer 出力先
-     * @throws IOException 出力エラー
-     */
-    public static void dumpDocType(Writer writer) throws IOException{
-        writer.append("<!DOCTYPE village SYSTEM ");
-        writer.append('"');
-        writer.append(ORIG_DTD);
-        writer.append('"');
-        writer.append(" >");
-        return;
-    }
-
-    /**
-     * オリジナルNameSpace宣言を出力する。
-     * @param writer 出力先
-     * @throws IOException 出力エラー
-     */
-    public static void dumpNameSpaceDecl(Writer writer)
-            throws IOException{
-        attrOut(writer, "xmlns", ORIG_NS);
-        return;
-    }
-
-    /**
-     * スキーマNameSpace宣言を出力する。
-     * @param writer 出力先
-     * @throws IOException 出力エラー
-     */
-    public static void dumpSiNameSpaceDecl(Writer writer)
-            throws IOException{
-        attrOut(writer, "xmlns:xsi", SCHEMA_NS);
-        return;
-    }
-
-    /**
-     * スキーマ位置指定を出力する。
-     * @param writer 出力先
-     * @throws IOException 出力エラー
-     */
-    public static void dumpSchemeLocation(Writer writer)
-            throws IOException{
-        attrOut(writer,
-                "xsi:schemaLocation",
-                ORIG_NS + " " + ORIG_SCHEME);
-        return;
-    }
-
-    /**
-     * インデント用空白を出力する。
-     * ネスト単位は空白2文字
-     * @param writer 出力先
-     * @param level ネストレベル
-     * @throws IOException 出力エラー
-     */
-    public static void indent(Writer writer, int level) throws IOException{
-        for(int ct = 1; ct <= level; ct++){
-            writer.append(INDENT_UNIT);
-        }
-        return;
-    }
-
-    /**
-     * XML数値文字参照を出力する。
-     * @param writer 出力先
-     * @param chVal 出力文字
-     * @throws IOException 出力エラー
-     */
-    public static void charRefOut(Writer writer, char chVal)
-            throws IOException{
-        if(chVal == '\u0020'){
-            writer.append("&#x20;");
-            return;
-        }
-
-        if(chVal == '\u0009'){
-            writer.append("&#x09;");
-            return;
-        }
-
-        int ival = 0xffff & ((int) chVal);
-        String hex = Integer.toHexString(ival);
-        if(hex.length() % 2 != 0) hex = "0" + hex;
-
-        writer.append("&#x");
-        writer.append(hex);
-        writer.append(";");
-
-        return;
-    }
-
-    /**
-     * 不正文字をXML出力する。
-     * @param writer 出力先
-     * @param chVal 不正文字
-     * @throws IOException 出力エラー
-     */
-    public static void dumpInvalidChar(Writer writer, char chVal)
-            throws IOException{
-        int hexVal;
-        hexVal = chVal & 0xff;
-        String hexBin = Integer.toHexString(hexVal);
-        if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
-
-        char replaceChar = '\ufffd';
-        if('\u0000' <= chVal && chVal <= '\u001f'){
-            replaceChar = (char)( chVal + '\u2400' );
-        }
-
-        writer.append("<rawdata");
-
-        writer.append(' ');
-        attrOut(writer, "encoding", "Shift_JIS");
-
-        writer.append(' ');
-        attrOut(writer, "hexBin", hexBin);
-
-        writer.append(" >");
-        writer.append(replaceChar);
-        writer.append("</rawdata>");
-    }
-
-    /**
-     * 任意の文字がXML規格上のホワイトスペースに属するか判定する。
-     * @param chVal 文字
-     * @return ホワイトスペースならtrue
-     */
-    public static boolean isWhiteSpace(char chVal){
-        switch(chVal){
-        case '\u0020':
-        case '\t':
-        case '\n':
-        case '\r':
-            return true;
-        default:
-            break;
-        }
-
-        return false;
-    }
-
-    /**
-     * 文字列を出力する。
-     * <ul>
-     * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。
-     * <li>連続したホワイトスペースの2文字目以降は文字参照化される。
-     * <li>スペースでないホワイトスペースは無条件に文字参照化される。
-     * <li>{@literal &, <, >, "}は無条件に文字参照化される。
-     * </ul>
-     * 参考:XML 1.0 規格 3.3.3節
-     * @param writer 出力先
-     * @param seq CDATA文字列
-     * @throws IOException 出力エラー
-     */
-    public static void textOut(Writer writer, CharSequence seq)
-            throws IOException{
-        int len = seq.length();
-
-        boolean leadSpace = false;
-
-        for(int pos = 0; pos < len; pos++){
-            char chVal = seq.charAt(pos);
-
-            if(isWhiteSpace(chVal)){
-                if(pos == 0 || pos >= len - 1 || leadSpace){
-                    charRefOut(writer, chVal);
-                }else if(chVal != '\u0020'){
-                    charRefOut(writer, chVal);
-                }else{
-                    writer.append(chVal);
-                }
-                leadSpace = true;
-            }else{
-                if(chVal == '&'){
-                    writer.append("&amp;");
-                }else if(chVal == '<'){
-                    writer.append("&lt;");
-                }else if(chVal == '>'){
-                    writer.append("&gt;");
-                }else if(chVal == '"'){
-                    writer.append("&quot;");
-                }else if(chVal == '\''){
-                    writer.append("&apos;");
-                }else if(chVal == BS_CHAR){
-                    writer.append('\u00a5');
-                }else if(chVal == '\u007e'){
-                    writer.append('\u203e');
-                }else if(Character.isISOControl(chVal)){
-                    dumpInvalidChar(writer, chVal);
-                }else{
-                    writer.append(chVal);
-                }
-                leadSpace = false;
-            }
-        }
-
-        return;
-    }
-
-    /**
-     * 属性を出力する。
-     * @param writer 出力先
-     * @param name 属性名
-     * @param value 属性値
-     * @throws IOException 出力エラー
-     */
-    public static void attrOut(Writer writer,
-                                CharSequence name,
-                                CharSequence value)
-            throws IOException{
-        StringBuilder newValue = new StringBuilder(value);
-        for(int pt = 0; pt < newValue.length(); pt++){
-            char chVal = newValue.charAt(pt);
-            if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;
-            if(Character.isISOControl(chVal)){
-                newValue.setCharAt(pt, (char)('\u2400' + chVal));
-            }
-        }
-
-        writer.append(name);
-        writer.append('=');
-        writer.append('"');
-        textOut(writer, newValue);
-        writer.append('"');
-        return;
-    }
-
-    /**
-     * xsd:time形式の時刻属性を出力する。
-     * タイムゾーンは「+09:00」固定
-     * @param writer 出力先
-     * @param name 属性名
-     * @param hour 時間
-     * @param minute 分
-     * @throws IOException 出力エラー
-     */
-    public static void timeAttrOut(Writer writer,
-                                     CharSequence name,
-                                     int hour, int minute)
-            throws IOException{
-        String cmtTime =
-                MessageFormat
-                .format("{0,number,#00}:{1,number,#00}:00+09:00",
-                        hour, minute);
-        attrOut(writer, name, cmtTime);
-        return;
-    }
-
-    /**
-     * xsd:gMonthDay形式の日付属性を出力する。
-     * タイムゾーンは「+09:00」固定
-     * @param writer 出力先
-     * @param name 属性名
-     * @param month 月
-     * @param day 日
-     * @throws IOException 出力エラー
-     */
-    public static void dateAttrOut(Writer writer,
-                                     CharSequence name,
-                                     int month, int day)
-            throws IOException{
-        String dateAttr =
-                MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",
-                                     month, day);
-        attrOut(writer, name, dateAttr);
-        return;
-    }
-
-    /**
-     * xsd:dateTime形式の日付時刻属性を出力する。
-     * タイムゾーンは「+09:00」固定
-     * @param writer 出力先
-     * @param name 属性名
-     * @param epochMs エポック時刻
-     * @throws IOException 出力エラー
-     */
-    public static void dateTimeAttr(Writer writer,
-                                      CharSequence name,
-                                      long epochMs)
-            throws IOException{
-        Calendar calendar = new GregorianCalendar(TZ_TOKYO);
-
-        calendar.setTimeInMillis(epochMs);
-        int year = calendar.get(Calendar.YEAR);
-        int month = calendar.get(Calendar.MONTH) + 1;
-        int day = calendar.get(Calendar.DATE);
-        int hour = calendar.get(Calendar.HOUR_OF_DAY);
-        int minute = calendar.get(Calendar.MINUTE);
-        int sec = calendar.get(Calendar.SECOND);
-        int msec = calendar.get(Calendar.MILLISECOND);
-
-        String attrVal = MessageFormat.format(
-                 "{0,number,#0000}-{1,number,#00}-{2,number,#00}"
-                +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"
-                +".{6,number,#000}+09:00",
-                year, month, day, hour, minute, sec, msec);
-
-        attrOut(writer, name, attrVal);
-
-        return;
-    }
-
-    /**
-     * デコードエラー情報をrawdataタグで出力する。
-     * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。
-     * @param writer 出力先
-     * @param errorInfo デコードエラー
-     * @throws IOException 出力エラー
-     */
-    public static void dumpErrorInfo(Writer writer,
-                                       DecodeErrorInfo errorInfo)
-            throws IOException{
-        int hexVal;
-        hexVal = errorInfo.getRawByte1st() & 0xff;
-        if(errorInfo.has2nd()){
-            hexVal <<= 8;
-            hexVal |= errorInfo.getRawByte2nd() & 0xff;
-        }
-
-        String hexBin = Integer.toHexString(hexVal);
-        if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;
-
-        char replaceChar = Win31j.getWin31jChar(errorInfo);
-
-        writer.append("<rawdata");
-
-        writer.append(' ');
-        attrOut(writer, "encoding", "Shift_JIS");
-
-        writer.append(' ');
-        attrOut(writer, "hexBin", hexBin);
-
-        writer.append(" >");
-        writer.append(replaceChar);
-        writer.append("</rawdata>");
-
-        return;
-    }
-
-    /**
-     * デコードエラー込みのテキストを出力する。
-     * @param writer 出力先
-     * @param content テキスト
-     * @throws IOException 出力エラー
-     */
-    public static void dumpDecodedContent(Writer writer,
-                                             DecodedContent content)
-            throws IOException{
-        if( ! content.hasDecodeError() ){
-            textOut(writer, content);
-            return;
-        }
-
-        int last = 0;
-
-        List<DecodeErrorInfo> errList = content.getDecodeErrorList();
-        for(DecodeErrorInfo err : errList){
-            int charPos = err.getCharPosition();
-            CharSequence line = content.subSequence(last, charPos);
-            textOut(writer, line);
-            dumpErrorInfo(writer, err);
-            last = charPos + 1;
-        }
-
-        CharSequence line = content.subSequence(last, content.length());
-        textOut(writer, line);
-
-        return;
-    }
-
-    /**
-     * 村情報をXML形式で出力する。
-     * @param writer 出力先
-     * @param villageData 村情報
-     * @throws IOException 出力エラー
-     */
-    public static void dumpVillageData(Writer writer,
-                                         VillageData villageData)
-            throws IOException{
-        writer.append("<?xml");
-        writer.append(' ');
-        attrOut(writer, "version", "1.0");
-        writer.append(' ');
-        attrOut(writer, "encoding", "UTF-8");
-        writer.append(" ?>\n\n");
-
-        writer.append("<!--\n");
-        writer.append("  人狼BBSアーカイブ\n");
-        writer.append("  http://jindolf.sourceforge.jp/\n");
-        writer.append("-->\n\n");
-
-        dumpDocType(writer);
-        writer.append("\n\n");
-
-        villageData.dumpXml(writer);
-
-        writer.append("\n<!-- EOF -->\n");
-
-        writer.flush();
-
-        return;
-    }
-
 }
diff --git a/src/test/java/jp/sourceforge/jindolf/archiver/XmlOutTest.java b/src/test/java/jp/sourceforge/jindolf/archiver/XmlOutTest.java
new file mode 100644 (file)
index 0000000..b2786d8
--- /dev/null
@@ -0,0 +1,617 @@
+/*
+ */
+
+package jp.sourceforge.jindolf.archiver;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import jp.sourceforge.jindolf.parser.DecodeErrorInfo;
+import jp.sourceforge.jindolf.parser.DecodedContent;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class XmlOutTest {
+
+    public XmlOutTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of isWhiteSpace method, of class XmlUtils.
+     */
+    @Test
+    public void testIsWhiteSpace() {
+        System.out.println("isWhiteSpace");
+
+        assertTrue(XmlOut.isWhiteSpace('\u0020'));
+        assertTrue(XmlOut.isWhiteSpace('\t'));
+        assertTrue(XmlOut.isWhiteSpace('\n'));
+        assertTrue(XmlOut.isWhiteSpace('\r'));
+
+        assertFalse(XmlOut.isWhiteSpace('\u0000'));
+        assertFalse(XmlOut.isWhiteSpace('\u3000'));
+        assertFalse(XmlOut.isWhiteSpace('A'));
+        assertFalse(XmlOut.isWhiteSpace('亜'));
+
+        return;
+    }
+
+    /**
+     * Test of dumpDocType method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpDocType() throws Exception {
+        System.out.println("dumpDocType");
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpDocType();
+        xmlOut.close();
+
+        assertEquals("<!DOCTYPE village SYSTEM \"http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-110421.dtd\" >", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dumpNameSpaceDecl method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpNameSpaceDecl() throws Exception {
+        System.out.println("dumpNameSpaceDecl");
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpNameSpaceDecl();
+        xmlOut.close();
+
+        assertEquals("xmlns=\"http://jindolf.sourceforge.jp/xml/ns/501\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dumpSiNameSpaceDecl method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpSiNameSpaceDecl() throws Exception {
+        System.out.println("dumpSiNameSpaceDecl");
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpSiNameSpaceDecl();
+        xmlOut.close();
+
+        assertEquals("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dumpSchemeLocation method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpSchemeLocation() throws Exception {
+        System.out.println("dumpSchemeLocation");
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpSchemeLocation();
+        xmlOut.close();
+
+        assertEquals("xsi:schemaLocation=\"http://jindolf.sourceforge.jp/xml/ns/501 http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-110421.xsd\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of indent method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testIndent() throws Exception {
+        System.out.println("indent");
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.indent(1);
+        xmlOut.close();
+        assertEquals("  ", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.indent(2);
+        xmlOut.close();
+        assertEquals("    ", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.indent(0);
+        xmlOut.close();
+        assertEquals("", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of charRefOut method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testCharRefOut() throws Exception {
+        System.out.println("charRefOut");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u0020');
+        xmlOut.close();
+        assertEquals("&#x20;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u0009');
+        xmlOut.close();
+        assertEquals("&#x09;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u0000');
+        xmlOut.close();
+        assertEquals("&#x00;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u0001');
+        xmlOut.close();
+        assertEquals("&#x01;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u00ff');
+        xmlOut.close();
+        assertEquals("&#xff;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u0100');
+        xmlOut.close();
+        assertEquals("&#x0100;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\u1000');
+        xmlOut.close();
+        assertEquals("&#x1000;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('\ud800');
+        xmlOut.close();
+        assertEquals("&#xd800;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charRefOut('亜');
+        xmlOut.close();
+        assertEquals("&#x4e9c;", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dumpRawData method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpRawData() throws Exception {
+        System.out.println("dumpInvalidChar");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpRawData('\u0000');
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"00\" >\u2400</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpRawData('\u001f');
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"1f\" >\u241f</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpRawData('\u0020');
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"20\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpRawData('\u00ff');
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"ff\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dumpRawData('\u0100');
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"00\" >\ufffd</rawdata>", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of charDataOut method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testCharDataOut() throws Exception {
+        System.out.println("textOut");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("ABC");
+        xmlOut.close();
+        assertEquals("ABC", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("&<>\"'");
+        xmlOut.close();
+        assertEquals("&amp;&lt;&gt;&quot;&apos;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0008B");
+        xmlOut.close();
+        assertEquals("A<rawdata encoding=\"Shift_JIS\" hexBin=\"08\" >\u2408</rawdata>B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u007fB");
+        xmlOut.close();
+        assertEquals("A\u007fB", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u009fB");
+        xmlOut.close();
+        assertEquals("A\u009fB", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u00a0B");
+        xmlOut.close();
+        assertEquals("A\u00a0B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\\B");
+        xmlOut.close();
+        assertEquals("A¥B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A~B");
+        xmlOut.close();
+        assertEquals("A‾B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0020\u0020");
+        xmlOut.close();
+        assertEquals("A &#x20;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0020");
+        xmlOut.close();
+        assertEquals("A&#x20;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0020B");
+        xmlOut.close();
+        assertEquals("A B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0020");
+        xmlOut.close();
+        assertEquals("A&#x20;", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0020\u0020B");
+        xmlOut.close();
+        assertEquals("A &#x20;B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("\u0020\u0020B");
+        xmlOut.close();
+        assertEquals("&#x20;&#x20;B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("A\u0020\u0020\u0020B");
+        xmlOut.close();
+        assertEquals("A &#x20;&#x20;B", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.charDataOut("\u0020\u0020\u0020B");
+        xmlOut.close();
+        assertEquals("&#x20;&#x20;&#x20;B", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of attrOut method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testAttrOut() throws Exception {
+        System.out.println("attrOut");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.attrOut("A", "B");
+        xmlOut.close();
+        assertEquals("A=\"B\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.attrOut("A", "B\u0008C");
+        xmlOut.close();
+        assertEquals("A=\"B\u0008C\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.attrOut("A", "B\nC\rD\tE");
+        xmlOut.close();
+        assertEquals("A=\"B\nC\rD\tE\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of timeAttrOut method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testTimeAttrOut() throws Exception {
+        System.out.println("timeAttrOut");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.timeAttrOut("A", 0, 0);
+        xmlOut.close();
+        assertEquals("A=\"00:00:00+09:00\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.timeAttrOut("A", 9, 9);
+        xmlOut.close();
+        assertEquals("A=\"09:09:00+09:00\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.timeAttrOut("A", 23, 59);
+        xmlOut.close();
+        assertEquals("A=\"23:59:00+09:00\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dateAttrOut method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDateAttrOut() throws Exception {
+        System.out.println("dateAttrOut");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dateAttrOut("A", 1, 1);
+        xmlOut.close();
+        assertEquals("A=\"--01-01+09:00\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dateAttrOut("A", 2, 29);
+        xmlOut.close();
+        assertEquals("A=\"--02-29+09:00\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dateAttrOut("A", 12, 31);
+        xmlOut.close();
+        assertEquals("A=\"--12-31+09:00\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dateTimeAttr method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDateTimeAttr() throws Exception {
+        System.out.println("dateTimeAttr");
+
+        Writer writer;
+        XmlOut xmlOut;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dateTimeAttr("A", 0L);
+        xmlOut.close();
+        assertEquals("A=\"1970-01-01T09:00:00.000+09:00\"", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        xmlOut.dateTimeAttr("A", 1466871486000L);
+        xmlOut.close();
+        assertEquals("A=\"2016-06-26T01:18:06.000+09:00\"", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dumpErrorInfo method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpErrorInfo() throws Exception {
+        System.out.println("dumpErrorInfo");
+
+        Writer writer;
+        XmlOut xmlOut;
+        DecodeErrorInfo errorInfo;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        errorInfo = new DecodeErrorInfo(0, (byte) 0x00);
+        xmlOut.dumpErrorInfo(errorInfo);
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"00\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        errorInfo = new DecodeErrorInfo(0, (byte) 0x0f);
+        xmlOut.dumpErrorInfo(errorInfo);
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"0f\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        errorInfo = new DecodeErrorInfo(0, (byte) 0x10);
+        xmlOut.dumpErrorInfo(errorInfo);
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"10\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        errorInfo = new DecodeErrorInfo(0, (byte) 0xff);
+        xmlOut.dumpErrorInfo(errorInfo);
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"ff\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        errorInfo = new DecodeErrorInfo(0, (byte) 0xee, (byte) 0xef); //92区
+        xmlOut.dumpErrorInfo(errorInfo);
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"eeef\" >\u2170</rawdata>", writer.toString());
+
+        return;
+    }
+
+    /**
+     * Test of dumpDecodedContent method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testDumpDecodedContent() throws Exception {
+        System.out.println("dumpDecodedContent");
+
+        Writer writer;
+        XmlOut xmlOut;
+        DecodedContent content;
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        content = new DecodedContent("ABC");
+        xmlOut.dumpDecodedContent(content);
+        xmlOut.close();
+        assertEquals("ABC", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        content = new DecodedContent();
+        content.append("AB");
+        content.addDecodeError((byte)0x08);
+        content.append("CD");
+        xmlOut.dumpDecodedContent(content);
+        xmlOut.close();
+        assertEquals("AB<rawdata encoding=\"Shift_JIS\" hexBin=\"08\" >\ufffd</rawdata>CD", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        content = new DecodedContent();
+        content.append("AB");
+        content.addDecodeError((byte)0x08);
+        xmlOut.dumpDecodedContent(content);
+        xmlOut.close();
+        assertEquals("AB<rawdata encoding=\"Shift_JIS\" hexBin=\"08\" >\ufffd</rawdata>", writer.toString());
+
+        writer = new StringWriter();
+        xmlOut = new XmlOut(writer);
+        content = new DecodedContent();
+        content.addDecodeError((byte)0x08);
+        content.append("CD");
+        xmlOut.dumpDecodedContent(content);
+        xmlOut.close();
+        assertEquals("<rawdata encoding=\"Shift_JIS\" hexBin=\"08\" >\ufffd</rawdata>CD", writer.toString());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jindolf/archiver/XmlUtilsTest.java b/src/test/java/jp/sourceforge/jindolf/archiver/XmlUtilsTest.java
new file mode 100644 (file)
index 0000000..2644f26
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ */
+
+package jp.sourceforge.jindolf.archiver;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.validation.Validator;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class XmlUtilsTest {
+
+    public XmlUtilsTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of createDocumentBuilder method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testCreateDocumentBuilder() throws Exception {
+        System.out.println("createDocumentBuilder");
+
+        DocumentBuilder result;
+
+        result = XmlUtils.createDocumentBuilder();
+        assertNotNull(result);
+
+        return;
+    }
+
+    /**
+     * Test of createValidator method, of class XmlUtils.
+     * @throws java.lang.Exception
+     */
+    @Test
+    public void testCreateValidator() throws Exception {
+        System.out.println("createValidator");
+
+        Validator result;
+
+        result = XmlUtils.createValidator();
+        assertNotNull(result);
+
+        return;
+    }
+
+}