필기노트

JAVA 전문 생성과 파싱(반복, 저장, XML) 본문

JAVA

JAVA 전문 생성과 파싱(반복, 저장, XML)

우퐁코기 2023. 2. 21. 06:54
반응형

reference 점프 투 자바 - B1 전문 생성과 파싱 https://wikidocs.net/266

 

B1 전문 생성과 파싱

대규모의 프로젝트를 진행하다보면 항상 서로 다른 시스템끼리 데이터를 주고 받아야 하는 상황이 생기게 된다. 이 때 가장 많이 사용하는 것이 바로 전문을 주고 받는 방법이다. 여기…

wikidocs.net

전문 생성과 파싱에 대한 내용은 위 글에서 너무 잘 설명하고 있어서 생략한다.

 

본 글은 위 글에서 3가지를 더 할 것이다.

1. 반복구간을 설정하고 반복구간만큼 파싱하는 방법

2. 파싱한 데이터를 테이블에 저장하는 방법

3. XML을 파싱해서 객체를 생성하는 방법

 

총 6개의 파일이 존재한다.

1. SubItem.java - 반복구간을 파싱하기 위한 객체

2. Item.java - 전문 데이터를 담을 객체

3. cust.xml - 전문을 정의한다.

4. LoadXml.java - XML을 파싱해서 Packet 객체를 생성한다.

5. Packet.java - Item 객체들을 담는다.

6. FullText.java - 전문을 요청하고 응답을 받는다.

 

1. SubItem

public class SubItem {
    private String id;
    private String name;
    private int length;
    private String value;

    private String field;
    private String type;

    private ArrayList<String> values = new ArrayList<>();


    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getLength() { return length; }
    public void setLength(int length) { this.length = length; }
    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }
    public String getField() { return field; }
    public void setField(String field) { this.field = field; }
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    public String getValues(int i) { return values.get(i); }
    public void addValues(String value) { this.values.add(value); }


    public String raw() {
        StringBuffer padded = new StringBuffer(this.value);
        while (padded.toString().getBytes().length < this.length) {
            padded.append(" ");
        }
        return padded.toString();
    }
}
  • private String field; -> 데이터베이스에 저장하기 위한 필드명 속성이 추가되었다.
  • private String type; -> 데이터베이스에 저장하기 위한 필드타입 속성이 추가되었다. (문자 or 숫자)
  • private ArrayList<String> values -> 파싱한 반복구간 필드의 값들을 가변으로 담는다.

 

2. Item

public class Item extends SubItem {
    private boolean group;
    private int count;

    private String table;

    private ArrayList<SubItem> subItems = new ArrayList<>();


    public boolean getGroup() { return group; }
    public void setGroup(boolean group) { this.group = group; }
    public int getCount() { return count; }
    public void setCount(int count) { this.count = count; }
    public String getTable() { return table; }
    public void setTable(String table) { this.table = table; }
    public ArrayList<SubItem> getSubItems() { return subItems; }
    public void setSubItems(ArrayList<SubItem> subItems) { this.subItems = subItems; }


    public static Item create( String id, String name, int length, String value
                             , boolean group
                             , String table, String field, String type ) {
        Item item = new Item();
        item.setId(id);
        item.setName(name);
        item.setLength(length);
        item.setValue(value);

        item.setGroup(group);

        item.setTable(table);
        item.setType(type);
        item.setField(field);

        return item;
    }
}
  • public class Item extends SubItem // 반복구간을 추가하면서 기존의 Item 속성들을 SubItem으로 상속 받고 있다.
  • private ArrayList<SubItem> subItems // 또한 ArrayList로 SubItem에 반복구간의 항목들을 가변으로 담는다.
  • private boolean group; // 반복구간이 존재하는지 여부를 판단한다.
  • private int count; // 반복구간의 수를 저장한다.
  • private String table; // 반복구간의 데이터를 저장하기 위한 테이블을 저장한다.

 

3. cust.xml

<?xml version="1.0" encoding="UTF-8"?>
<msg>
<input>
    <item id="name"     name="이름"        length="20" />
    <item id="jumin"    name="주민번호"     length="13" />
</input>
<output table="tbl">
    <item id="name"         name="이름"       length="20" field="WH001"   type="C" />
    <item id="jumin"        name="주민번호"     length="13" field="WH002"   type="C" />
    <item id="nbr_cnt"      name="전화번호 건수"  length="3"  field="WH003"   type="N" />
    <item group="nbr_grp"   name="전화번호 그룹"  length="3"  table="tblnbr">
        <item id="type"     name="타입"       length="3"  field="WN001"   type="C" />
        <item id="number"   name="번호"       length="11" field="WN002"   type="C" />
    </item>
    <item id="adr_cnt"      name="주소 건수"    length="3"  field="WH004"   type="N" />
    <item group="adr_grp"   name="주소 그룹"    length="3"  table="tbladr">
        <item id="type"     name="타입"       length="3"  field="WJ001"   type="C" />
        <item id="juso"     name="주소"       length="50" field="WJ002"   type="C" />
    </item>
</output>
</msg>
  • XML에 전문을 정의해서 파싱함으로써 동적으로 객체를 생성해 주고 전문 가독성 또한 올라가고 유지보수에 용이해진다.
  • recvPacket.addItem(Item.create("주소", 50, null)); // 기존에는 이렇게 직접 객체를 생성.
  • <input> // 요청 전문을 정의하고 있다.
  • <output> // 응답 전문을 정의하고 있다.
  • table // 데이터베이스에 저장할 테이블명을 명시하고 있다.
  • field // 데이터베이스에 저장할 필드명을 명시하고 있다.
  • type // 데이터베이스에 저장할 때 필드의 타입을 명시하고 있다. (C(har) -> 문자, N(umeric) -> 숫자)
  • nbr_cnt, nbr_grp // 전화번호 그룹 데이터가 오기 전에는 전화번호 건수를 먼저 받기로 정의되어 있다.

 

4. LoadXml

public class LoadXml {
    private Packet reqPckt = new Packet();
    private Packet resPckt = new Packet();

    public Packet getReqPckt() {
        return reqPckt;
    }
    public Packet getResPckt() {
        return resPckt;
    }


    public LoadXml(String pathname) {
        try {
            // 파일경로
            File file = new File(pathname);

            // 파일읽기
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dbuilder = dbFactory.newDocumentBuilder();
            Document doc = dbuilder.parse(file);
            Element root = doc.getDocumentElement();

            if (root.hasChildNodes()) {
                NodeList nodes = root.getChildNodes();

                for (int i = 0; i < nodes.getLength(); i++) {
                    String nodeName = nodes.item(i).getNodeName();
                    if(nodeName.equals("input"))
                        readNode(nodes.item(i), reqPckt);
                    else if(nodeName.equals("output"))
                        readNode(nodes.item(i), resPckt);
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    . . . (생략)

}
  • DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); // 팩토리 생성
  • DocumentBuilder dbuilder = dbFactory.newDocumentBuilder(); // 팩토리로부터 빌더 생성
  • Document doc = dbuilder.parse(file); // 빌더를 통해 XML 문서를 파싱해서 Document 객체로 가져온다.
  • Element root = doc.getDocumentElement(); // 최상위 노드 가져오기 -> <msg>
  • if (root.hasChildNodes()) { // 자식 노드를 가지고 있는지 확인
  • NodeList nodes = root.getChildNodes(); // 자식 노드들을 노드리스트에 담는다. -> <input>, <output>
  • for (int i = 0; i < nodes.getLength(); i++) { // 노드 길이만큼 반복
  • String nodeName = nodes.item(i).getNodeName(); // 해당 노드의 태그 값을 가져온다.
  • if(nodeName.equals("input")) readNode(nodes.item(i), reqPckt); // <input>이냐 <output>이냐에 따라 다른 Packet에 담아준다.

readNode

public class LoadXml {
    
    . . . (생략)

    private static void readNode(Node node, Packet packet, boolean is_group) {
        Map<String, String> map = new HashMap<>();
        boolean group = false;

        if (node.hasAttributes()) {
            Element element = (Element) node;

            String id = element.getAttribute("id");
            String name = element.getAttribute("name");
            int length = element.getAttribute("length").equals("") ? 0 : Integer.parseInt(element.getAttribute("length"));

            group = element.getAttribute("group").equals("") ? false : true;

            String table = element.getAttribute("table");
            String field = element.getAttribute("field");
            String type = element.getAttribute("type");

            if (node.getNodeName().equals("output"))
                packet.setTable(table);
            else if (is_group == false)
                packet.addItems(Item.create(id, name, length, null, group, table, field, type));
            else if (is_group == true)
                packet.getItem(packet.getItemsSize()-1).getSubItems()
                        .add(Item.create(id, name, length, null, false, table, field, type));
        }

        if (node.hasChildNodes()) {
            NodeList childs = node.getChildNodes();

            for (int i = 0; i < childs.getLength(); i++) {
                Node child = childs.item(i);

                readNode(child, packet, group);
            }
        }
    }
}
  • if (node.hasAttributes()) { // 해당 노드에 속성이 있는지 본다.
  • Element element = (Element) node; // 노드를 엘리먼트로 치환시키고
  • String id = element.getAttribute("id"); // 해당 속성들을 받아온다.
  • if (node.getNodeName().equals("output")) packet.setTable(table); // 응답전문 항목들의 테이블은 Item의 상위 객체인 Packet 객체에 저장해야 한다. (반복구간 항목들의 테이블은 subItem의 상위 객체인 Item에 저장)
  • else if (is_group == false) // 반복구간이 아닐 땐 아래와 같은 Packet 객체를 생성한다.
    packet.addItems(Item.create(id, name, length, null, group, table, field, type));
  • else if (is_group == true) // 반복구간의 항목을 객체생성 할 때는 마지막 Item을 가지고 온다. (반복구간을 readNode 하기 전에 마지막에 생성된 Item은 그룹아이템이기 때문), 가지고 온 마지막 Item의 subItem에 반복구간의 항목을 아래와 같이 객체 생성한다.
    packet.getItem(packet.getItemsSize()-1).getSubItems().add(Item.create(id, name, length, null, false, table, field, type)); 
  • if (node.hasChildNodes()) { // 해당 노드의 자식 노드들이 있는지 본다.
  • for (int i = 0; i < childs.getLength(); i++) Node child = childs.item(i); // 해당 노드의 자식노드들을 하나씩 Node로 가져온다.
  • readNode(child, packet, group); // 가져온 노드를 readNode로 재귀함수 호출

 

5. Packet

public class Packet {
    private ArrayList<Item> items = new ArrayList<Item>();

    private String table;


    public ArrayList<Item> getItems() { return items; }
    public void addItems(Item item) { this.items.add(item); }
    public Item getItem(int index) { return items.get(index); }
    public int getItemsSize() { return items.size(); }
    public String getTable() { return table; }
    public void setTable(String table) { this.table = table; }


    public String raw() {
        StringBuffer result = new StringBuffer();
        for (Item item : items) {
            result.append(item.raw());
        }
        return result.toString();
    }

    public void set(Map<String, String> map) {
        for (String key : map.keySet()) {
            for (Item item : items) {
                if (item.getId().equals(key)) {
                    item.setValue(map.get(key));
                }
            }
        }
    }

    public void allPrint() {
        for (Item item : items) {
            if (item.getGroup() == false) {
                System.out.println("name : " + item.getName() + " , value : " + item.getValue());
            } else {
                if (item.getCount() == 0)
                    System.out.println("name : " + item.getName() + " , value : " + item.getValue());
                for (int i = 0; i < item.getCount(); i++) {
                    for (SubItem subItem : item.getSubItems())
                        System.out.println("name : " + subItem.getName() + " , value : " + subItem.getValues(i));
                }
            }
        }
    }
}
  • private String table; // 응답전문의 데이터를 저장하기 위한 테이블 속성이 추가되었다.
  • set(Map<string, string> map) // 전문 요청을 하고자 하는 항목의 id와 value를 map에 태워서 set을 호출하면 items를 돌면서 같은 id를 찾으면 value의 값을 셋팅한다.

parse

public class Packet {

    . . . (생략)

    public void parse(String data) {
        byte[] bdate = data.getBytes();
        int pos = 0;
        int count = 0;
        for (Item item : items) {
            if(item.getGroup() == false) {
                byte[] temp = new byte[item.getLength()];
                System.arraycopy(bdate, pos, temp, 0, item.getLength());
                pos += item.getLength();
                item.setValue(new String(temp));
                if (item.getName().indexOf("건수") != -1) {
                    count = Integer.parseInt(item.getValue());
                }
            } else {
                item.setCount(count);
                for (int i = 0; i < item.getCount(); i++) {
                    for (SubItem subItem : item.getSubItems()) {
                        byte[] temp = new byte[subItem.getLength()];
                        System.arraycopy(bdate, pos, temp, 0, subItem.getLength());
                        pos += subItem.getLength();
                        subItem.addValues(new String(temp));
                    }
                }
            }
        }
    }
}
  • 기존 parse에서 반복구간을 파싱하기 위한 코드가 추가되었다.
  • 그룹데이터가 오기 전에는 항상 건수가 먼저 오기 때문에 아래와 같이 건수 필드의 값을 따로 가지고 있다가
  • if (item.getName().indexOf("건수") != -1) count = Integer.parseInt(item.getValue());
  • 그룹 데이터가 오면 가지고 있던 건수를 item.setCount(count)하고 반복구문을 count만큼 돌린다.
  • for (SubItem subItem : item.getSubItems()) // 그룹데이터의 항목 개수만큼 반복문을 다시 돌린다.
  • subItem.addValues(new String(temp)); // 해당 항목에 count만큼 값이 가변적으로 들어올 것이다.

toDatabase

public class Packet {
    
    . . . (생략)

    public void toDatabase() {
        String table = "";
        StringBuffer fields = new StringBuffer();
        StringBuffer values = new StringBuffer();

        for (Item item : items) {
            if (item.getGroup() == false) {
                fields.append(item.getField() + ",");
                if(item.getType().equals("C"))
                    values.append("'"+item.getValue().replace(" ", "") + "',");
                else if (item.getType().equals("N"))
                    values.append(item.getValue().replace(" ", "") + ",");
            }
        }
        if(fields.length() > 0) fields.deleteCharAt(fields.length()-1);
        if(values.length() > 0) values.deleteCharAt(values.length()-1);
        System.out.println("insert into " + this.table + "(" + fields.toString() + ") valeus (" + values.toString() + ")");

        fields.delete(0, fields.length());
        values.delete(0, values.length());

        for (Item item : items) {
            if (item.getGroup() == true) {
                for (int i = 0; i < item.getCount(); i++) {
                    table = item.getTable();
                    for (SubItem subItem : item.getSubItems()) {
                        fields.append(subItem.getField() + ",");
                        if(subItem.getType().equals("C"))
                            values.append("'"+ subItem.getValues(i).replace(" ", "") + "',");
                        else if (subItem.getType().equals("N"))
                            values.append(subItem.getValues(i).replace(" ", "") + ",");
                    }
                    if(fields.length() > 0) fields.deleteCharAt(fields.length()-1);
                    if(values.length() > 0) values.deleteCharAt(values.length()-1);
                    System.out.println("insert into " + table + "(" + fields.toString() + ") valeus (" + values.toString() + ")");
                    fields.delete(0, fields.length());
                    values.delete(0, values.length());
                }
            }
        }
    }
}
  • 먼저 응답전문의 항목들부터 테이블 인설트 구문을 뽑아내고
  • 다음 반복구간의 항목들을 테이블 인설트 구문을 뽑아낸다.
  • type으로 문자 타입 필드인지 숫자 타입 필드인지 확인하고 있다.

 

6. FullText

public class FullText {
    public static void main(String[] args) {
        LoadXml loadXml = new LoadXml(Paths.get("").toAbsolutePath()+"/src/main/resources/templates/cust.xml");
        Packet reqPacket = loadXml.getReqPckt();
        Packet resPacket = loadXml.getResPckt();

        System.out.println("요청");
        reqPacket.allPrint();
        System.out.println("응답");
        resPacket.allPrint();

        Map<String, String> map = new HashMap<>();
        map.put("name", "홍길동");
        map.put("jumin", "6666667777777");

        reqPacket.set(map);

        System.out.println("");
        System.out.println("[" + reqPacket.raw() + "]");

        resPacket.parse("홍길동           6666667777777003phn01099998888hom01077776666cpn01055554444002hom서울시 송파구 잠실동 123-3               cpn서울시 송파구 잠실동 456-7               ");

        System.out.println("");
        resPacket.allPrint();

        System.out.println("");
        resPacket.toDatabase();
    }
}

LoadXml loadXml = new LoadXml // 생성자로 XML을 파싱해서 요청 패킷 객체와 응답 패킷 객체를 받아오고 있다. (전문 송수신이 빈번 할 시 생성자보다는 static으로 메모리에 띄워놓고 가져다 쓰는 편이 좋다.)

 

reqPacket.set(map) // map에 요청하고자 하는 항목들을 담아서 요청 패킷 객체에 셋팅하고 있다.

 

resPacket.parse // 응답 받은 전문을 응답 패킷 객체에 파싱하고 있다.

 

resPacket.toDatabase(); // 응답 데이터들을 디비에 저장하고 있다.

반응형
Comments