공부방

XML 파싱 본문

React

XML 파싱

코딩 화이팅 2024. 9. 20. 00:24

fast-xml-parser

  • JavaScript로 작성된 빠르고 가벼운 XML 파싱 라이브러리
  • XML 데이터를 JavaScript 객체로 변환하거나 JavaScript 객체를 XML 문자열로 변환할 수 있다.
  • 특징
    • 빠른 파싱 : 이름처럼 매우 빠르게 XML을 파싱할 수 있으며, 성능이 중요한 환경에서 유용
    • 경량 : 종속성이 없고 가볍기 때문에 브라우저나 Node.js 환경 모두에서 사용하기 적합
    • 옵션 설정 가능 : XML을 파싱할 때 다양한 옵션을 설정할 수 있다. 예를 들어, XML 속성 파싱, 태그 및 속성 이름 변환, 텍스트 및 숫자 데이터 유형 처리 등에 대한 세부 제어가 가능
    • JSON 변환 : 파싱된 XML 데이터를 JSON 형식으로 변환할 수 있어, JSON API와의 상호 운용성이 좋음
    • 유효성 검사 : XML 문자열에 대한 유효성 검사를 수행하여 올바른 XML 형식인지 확인할 수 있다.
import { XMLParser } from 'fast-xml-parser';
  • XMLParser는 fast-xml-parser 라이브러리에서 XML 데이터를 파싱하는 도구
  • 이 라이브러리를 사용하여 XML을 JavaScript 객체로 변환 가능

 

export const parseXML = (xmlData) => {
...
    });
  • parseXML이라는 함수를 만들어 다른 파일에서도 사용할 수 있도록 export
  • xmlData는 이 함수가 호출될 때 전달받는 파라미터(입력값).
    • XML 데이터가 문자열 형태로 들어오게 된다.
    • 이 문자열에는 XML의 모든 태그, 속성, 값들을 포함한다.
<workout_file>
    <name>베이스 훈련</name>
    <workout>
        <Warmup Duration="240" PowerLow="0.32" PowerHigh="0.60"/>
    </workout>
</workout_file>
  • 예를 들어 이런 XML이 있다면
  • 이 XML 데이터를 문자열로 읽어온 것이 xmlData이다.
  • JavaScript에서는 이 XML 데이터가 하나의 큰 문자열로 처리된다.
    • "<workout_file><name>베이스 훈련</name><workout><Warmup Duration=\"240\" PowerLow=\"0.32\" PowerHigh=\"0.60\"/></workout></workout_file>"
    • 이런 식으로
  • 이 문자열은 현재는 단순한 텍스트이기 때문에, 그 안에 담긴 정보를 프로그램이 사용하려면 파싱하여 JavaScript 객체로 변환해야 한다.

 

export const parseXML = (xmlData) => {
    const parser = new XMLParser({
        ignoreAttributes: false,  // 속성을 파싱할 수 있도록 설정
        attributeNamePrefix: "",  // 속성 앞에 붙는 @_를 제거
    });
  • new XMLParser({...})는 XMLParser 클래스의 인스턴스를 생성
    • 이 인스턴스는 XML 데이터를 파싱하는 데 사용된다.
  • ignoreAttributes : XML 파일에는 태그에 다양한 속성들이 포함되어 있을 수 있기 때문에, XML 태그에 포함된 속성들을 파싱할지 여부를 설정해야한다.
<Warmup Duration="240" PowerLow="0.32" PowerHigh="0.60"/>
  • 예를 들어 이러한 XML 데이터가 있다고 하면.
  • 이 XML 태그에는 Duration, PowerLow, PowerHigh와 같은 속성들이 있다. 
  • 이 속성들은 태그의 주요 정보로, 이를 파싱해서 사용하려면 속성까지 객체로 변환해야 한다.

만약 ignoreAttributes: false가 없다면(ignoreAttributes 기본값은 true다)

  • 속성을 무시하고 태그 이름만 파싱하기 때문에, 속성에 대한 정보가 객체로 변환된지 않는다.
  • 파싱 결과는 다음과 같이 속성을 포함하지 않고 빈 객체로 나타날 수 있다.
{
  Warmup: {}
}

ignoreAttributes: fale를 설정한 경우

  • 태그의 속성들도 함께 파싱하여 객체로 변환
  • 파싱 결과는 다음과 같이 속성을 포함한 객체로 변환
{
  Warmup: {
    Duration: "240",
    PowerLow: "0.32",
    PowerHigh: "0.60"
  }
}
  • attributeNamePrefix : fase-xml-parser는 기본적으로 속성 이름 앞에 @_를 붙이는데, 이 옵션을 빈 문자열로 설정하여 @_를 제거하고 속성 이름을 그대로 사용할 수 있도록 설정

 

    const jsonObj = parser.parse(xmlData);
  • parser.pare(xmlData)를 호출하여 XML 문자열을 JavaScript 객체로 변환
    • 결과는 jsonObj에 저장
  • 파싱된 데이터는 XML의 계층 구조를 그대로 유지하는 JavaScript 객체가 된다.
  • 예를 들어, <work_file> 태그의 하위에 있는 내용들은 jsonObj.workout_file로 접근할 수 있게 된다.

 

    const workout = jsonObj.workout_file.workout;
    let stages = [];
  • jsonObj.workout_file.workout는 파싱된 XML 객체 중 <workout_file> 태그 안에 있는 <workout> 태그의 내용을 가져옴.
<workout_file>
    <name>베이스 훈련</name>
    <workout>
        <Warmup Duration="240" PowerLow="0.32" PowerHigh="0.60"/>
        <SteadyState Duration="60" Power="0.60"/>
    </workout>
</workout_file>
  • 이러한 XML 데이터가 있다고 하면
  • 파싱된 객체는 이런 구조를 가지게 된다.
{
    workout_file: {
        name: "베이스 훈련",
        workout: {
            Warmup: { Duration: "240", PowerLow: "0.32", PowerHigh: "0.60" },
            SteadyState: { Duration: "60", Power: "0.60" }
        }
    }
}
  • stages 배열은 각 운동 단계를 저장하기 위해 만들어진다.
  • 나중에 for 루프를 통해 <workout> 내부에 있는 모든 운동 단계의 정보를 배열에 하나씩 추가할 것임

 

for (const key in workout) {
    // 내용
}
  • for...in 루프를 사용하여 workout 객체의 모든 키를 순회
  • key는 workout 객체 안에 있는 각각의 속성 이름을 의미한다. 
    • 예를 들어 Warmup, SteadyState 등
  • 이 루프를 통해 각 워크 아웃 단계에 대한 정보를 하나씩 처리

 

if (Array.isArray(workout[key])) {
    // 각 단계가 배열인 경우
    workout[key].forEach((stage) => {
        stages.push({
            name: key,
            ...stage // 모든 속성 추가
        });
    });
}
  • Array.isArray(workout[key])는 현재 단계(workout[key])가 배열 형태인지 확인
else if (typeof workout[key] === 'object') {
    // 단일 객체로 존재하는 경우
    stages.push({
        name: key,
        ...workout[key] // 모든 속성 추가
    });
}
  • workout[key]가 배열이 아닌 단일 객체인 경우에 대한 처리

배열 객체와 단일 객체를 나누는 이유?

<workout>
    <SteadyState Duration="60" Power="0.6"/>
    <SteadyState Duration="60" Power="0.7"/>
</workout>
  • XML에서는 같은 이름의 태그가 여러번 나타날 수 있다.
  • 이 경우, fast-xml-parser는 같은 이름의 태그가 여러번 등장하면 이를 배열로 파싱하게 된다.
  • 반면에, 해당 태그가 XML에서 한 번만 나타나는 경우, 이를 단일 객체로 파싱하게 된다.

fase-xml-parser의 파싱 방식

여러번 나타나는 태그

{
    SteadyState: [
        { Duration: "60", Power: "0.6" },
        { Duration: "60", Power: "0.7" }
    ]
}
  • 같은 태그가 여러번 나타나는 경우, 파싱 결과는 위와 같은 배열 형태가 된다.

한 번 나타나는 태그

{
    Warmup: { Duration: "240", PowerLow: "0.32", PowerHigh: "0.60" }
}
  • 파싱 결과는 위와 같이 단일 객체 형태가 된다.

따라서 배열인지 아닌지를 나누는 이유

  • 태그가 배열일 때와 객체일 때 각각 다르게 처리하지 않으면, 파싱 결과에 따라 코드를 동적으로 작성할 수 없다. 
  • 예를 들어 Array.isArray를 통해 배열 여부를 확인하지 않고 동일한 방식으로 처리하려고 하면, 데이터 구조에 따라 코드가 제대로 동작하지 않을 수 있다.
  • 만약 모든 데이터를 배열로 가정하고 forEach나 map 같은 배열 메서드를 사용하려고 한다면, 태그가 한번만 나타날 때 단일 객체로 파싱된 경우 에러가 발생하게 된다.
    • 유연하고 에러 없는 코드를 만들기 위한 목적 

 

    for (const key in workout) {
        if (Array.isArray(workout[key])) {
            // 각 단계가 배열인 경우
            workout[key].forEach((stage) => {
                stages.push({
                    name: key,
                    ...stage // 모든 속성 추가
                });
            });
        } else if (typeof workout[key] === 'object') {
            // 단일 객체로 존재하는 경우
            stages.push({
                name: key,
                ...workout[key] // 모든 속성 추가
            });
        }
    }
<workout>
    <Warmup Duration="240" PowerLow="0.32" PowerHigh="0.60"/>
    <SteadyState Duration="60" Power="0.6"/>
    <SteadyState Duration="60" Power="0.7"/>
</workout>
  • 따라서 이러한 XML 데이터가 들어온다면
  • for...in 루프가 처음 만나는 키는 Warmup이고, 이는 단일 객체.
    • 따라서 else if 부분이 실행되어 stages에 이 객체를 추가
  • 두번째 키는 SteadyState이며, 이는 배열
    • 따라서 if 부분이 실행되고, 배열의 각 요소를 순회하며 stages에 추가
  • 밑은 파싱된 jsonObj 구조
{
    workout_file: {
        workout: {
            Warmup: { Duration: "240", PowerLow: "0.32", PowerHigh: "0.60" },
            SteadyState: [
                { Duration: "60", Power: "0.6" },
                { Duration: "60", Power: "0.7" }
            ]
        }
    }
}
[
    { name: "Warmup", Duration: "240", PowerLow: "0.32", PowerHigh: "0.60" },
    { name: "SteadyState", Duration: "60", Power: "0.6" },
    { name: "SteadyState", Duration: "60", Power: "0.7" }
]

결과적으로 stages 배열은 이와 같이 구성된다.

 

    const workoutDetails = {
        name: jsonObj.workout_file.name,
        description: jsonObj.workout_file.description,
        stages: stages
    };
    
    return workoutDetails;
};
  • 워크아웃 정보 객체 생성 : 파싱된 정보를 workoutDetails라는 객체에 정리
    • name : <name> 태그의 값을 저장
    • description : <description> 태그의 값을 저장
    • stages : 앞서 구성한 운동 단계들을 담고 있는 배열을 저장
  • 반환 : 최종적으로 workoutDetails 객체를 반환.
  • 이 객체는 다른 컴포넌트에서 워크아웃 정보를 시각화하거나 표시하는데 사용될 수 있다.

 

최종 코드

// XML 파일을 JavaScript 객체로 변환하여 필요한 데이터를 추출
import { XMLParser } from "fast-xml-parser";

// XMLData: XML 문자열
export const parseXML = (xmlData) => {
  // XMLParser를 이용하여 XML을 JavaScript 객체로 변환
  const parser = new XMLParser({
    ignoreAttributes: false, // 속성을 파싱할 수 있도록 설정
    attributeNamePrefix: "", // 속성 앞에 붙는 @_를 제거
  });
  // JavaScript 객체로 변환한 값을 jsonObj에 저장
  const jsonObj = parser.parse(xmlData);

  // 워크아웃 단계 추출
  const workout = jsonObj.workout_file.workout;
  let stages = [];

  // 단계별로 파싱
  for (const key in workout) {
    // 파싱된 결과가 배열 객체와 단일 객체로 나눠 배열에 넣어준다
    if (Array.isArray(workout[key])) {
      // 각 단계가 배열인 경우
      workout[key].forEach((stage) => {
        stages.push({
          name: key,
          ...stage, // 모든 속성 추가
        });
      });
    } else if (typeof workout[key] === "object") {
      // 단일 객체로 존재하는 경우
      stages.push({
        name: key,
        ...workout[key], // 모든 속성 추가
      });
    }
  }

  // 콘솔에 파싱된 데이터 출력
  console.log("Parsed stages:", stages);

  // 파싱된 정보들을 객체에 저장
  const workoutDetails = {
    name: jsonObj.workout_file.name,
    description: jsonObj.workout_file.description,
    stages: stages,
  };

  return workoutDetails;
};

'React' 카테고리의 다른 글

React Router-셋팅과 기본 라우팅  (0) 2024.10.22
차트 라이브러리-CanvasJsChart  (3) 2024.10.08
Card 컴포넌트 만들고 props, map  (0) 2024.08.17
import / export  (0) 2024.08.04
이미지 넣는 법 & public 폴더 이용하기  (0) 2024.07.28