ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ABAP — HTML/XML 속성값 추출 패턴
    SAP/소스 2026. 5. 27. 18:54
    ABAP — HTML/XML 속성값 추출 패턴

    ABAP — HTML/XML 속성값 추출 패턴

    SAP ABAP · 2026 · REGEX / cl_ixml / 문자열 파싱 3가지 방법 완전 정리

    1. 방법 비교 — 언제 무엇을 쓸까?

    방법 적합한 상황 장점 단점
    REGEX
    FIND … REGEX
    HTML (비정형), 특정 속성 1~2개만 필요 코드 단순, 빠름 중첩 구조 취약, 복잡한 HTML 오파싱 가능
    cl_ixml Well-formed XML / XHTML, 전체 속성 탐색 표준 DOM 지원, 구조적 탐색 가능 코드량 많음, HTML은 파싱 실패 가능
    문자열 파싱
    FIND / SPLIT
    구조가 단순·고정된 텍스트 외부 의존 없음, 이해 쉬움 패턴 변형에 취약

    💡 선택 기준: SAP PI/PO 메시지·IDoc XML → cl_ixml / 웹 스크래핑·HTML 파편 → REGEX / 단순 고정 포맷 텍스트 → 문자열 파싱

    2. REGEX FIND 문 속성값 추출

    FIND … REGEX … SUBMATCHES를 사용해 캡처 그룹으로 속성값을 뽑는다. HTML처럼 정형화되지 않은 텍스트에 가장 빠르게 적용할 수 있다.

    2-1. 단일 속성 추출

    " 대상: <input type="text" id="username" value="홍길동" />
    DATA: lv_html  TYPE string,
          lv_value TYPE string.
    
    lv_html = '<input type="text" id="username" value="홍길동" />'.
    
    " value 속성값 추출
    " 패턴: value\s*=\s*["']([^"']*)["']
    FIND REGEX 'value\s*=\s*["\x27]([^"\x27]*)["\x27]'
      IN lv_html
      SUBMATCHES lv_value.
    
    IF sy-subrc = 0.
      WRITE: / |value = { lv_value }|.   " → 홍길동
    ENDIF.

    💡 \x27 은 작은따옴표(') ASCII 16진수 표현이다. ABAP 문자열 리터럴 안에서 '를 직접 쓰면 문자열이 닫히므로 \x27로 대체한다.

    2-2. 여러 속성 한 번에 추출 (SUBMATCHES 다중)

    " 대상: <a href="https://example.com" class="link" target="_blank">링크</a>
    DATA: lv_src    TYPE string,
          lv_href   TYPE string,
          lv_class  TYPE string,
          lv_target TYPE string.
    
    lv_src = '<a href="https://example.com" class="link" target="_blank">링크</a>'.
    
    " href 추출
    FIND REGEX 'href\s*=\s*["\x27]([^"\x27]*)["\x27]'
      IN lv_src SUBMATCHES lv_href.
    
    " class 추출
    FIND REGEX 'class\s*=\s*["\x27]([^"\x27]*)["\x27]'
      IN lv_src SUBMATCHES lv_class.
    
    " target 추출
    FIND REGEX 'target\s*=\s*["\x27]([^"\x27]*)["\x27]'
      IN lv_src SUBMATCHES lv_target.
    
    WRITE: / |href   = { lv_href }|.    " → https://example.com
    WRITE: / |class  = { lv_class }|.   " → link
    WRITE: / |target = { lv_target }|.  " → _blank

    2-3. 반복 태그에서 전체 속성값 수집 (FIND ALL OCCURRENCES)

    " 대상: 여러 <img src="..."> 태그에서 src 전체 추출
    DATA: lv_html  TYPE string,
          lt_match TYPE match_result_tab,
          ls_match TYPE match_result,
          lv_src   TYPE string.
    
    lv_html = '<img src="logo.png" alt="로고"/>'
           && '<img src="banner.jpg" alt="배너"/>'
           && '<img src="icon.svg" alt="아이콘"/>'.
    
    " 모든 src 값 수집
    FIND ALL OCCURRENCES OF REGEX 'src\s*=\s*["\x27]([^"\x27]*)["\x27]'
      IN lv_html
      RESULTS lt_match.
    
    LOOP AT lt_match INTO ls_match.
      " 첫 번째 캡처 그룹 (submatches 첫 요소)
      lv_src = lv_html+ls_match-submatches[ 1 ]-offset(
                       ls_match-submatches[ 1 ]-length ).
      WRITE: / |src = { lv_src }|.
    ENDLOOP.
    " → logo.png / banner.jpg / icon.svg

    2-4. cl_abap_regex / cl_abap_matcher (OOP 방식)

    DATA: lo_regex   TYPE REF TO cl_abap_regex,
          lo_matcher TYPE REF TO cl_abap_matcher,
          lv_html    TYPE string,
          lv_result  TYPE string.
    
    lv_html = '<div data-id="42" data-name="테스트" class="box"></div>'.
    
    " data-id 추출
    lo_regex   = cl_abap_regex=>create( pattern     = 'data-id\s*=\s*["\x27]([^"\x27]*)["\x27]'
                                        ignore_case = abap_true ).
    lo_matcher = lo_regex->create_matcher( text = lv_html ).
    
    IF lo_matcher->find_next( ) = abap_true.
      lv_result = lo_matcher->get_submatch( 1 ).
      WRITE: / |data-id = { lv_result }|.   " → 42
    ENDIF.

    3. cl_ixml SAP 표준 XML DOM 파서

    cl_ixml은 SAP 표준 XML 파서로, Well-formed XML 문서를 DOM 트리로 파싱한다. PI/PO 메시지, IDoc XML, SAP 내부 XML 처리에 권장한다.

    3-1. 기본 구조 — 파서 생성 → 파싱 → DOM 탐색

    DATA: lv_xml      TYPE string,
          lo_ixml     TYPE REF TO if_ixml,
          lo_sfactory TYPE REF TO if_ixml_stream_factory,
          lo_istream  TYPE REF TO if_ixml_istream,
          lo_document TYPE REF TO if_ixml_document,
          lo_parser   TYPE REF TO if_ixml_parser,
          lv_xml_raw  TYPE xstring,
          lv_rc       TYPE i.
    
    lv_xml = '<?xml version="1.0" encoding="UTF-8"?>'
          && '<orders>'
          && '  <order id="1001" status="ACTIVE" amount="99.50">'
          && '    <customer cid="C01" name="홍길동"/>'
          && '  </order>'
          && '  <order id="1002" status="CLOSED" amount="150.00">'
          && '    <customer cid="C02" name="김철수"/>'
          && '  </order>'
          && '</orders>'.
    
    " ── 1. xstring 변환 ───────────────────────────────────────────────
    CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
      EXPORTING  text     = lv_xml
                 mimetype = 'text/xml'
                 encoding = 'UTF-8'
      IMPORTING  buffer   = lv_xml_raw
      EXCEPTIONS OTHERS   = 1.
    
    " ── 2. 파서 생성 ──────────────────────────────────────────────────
    lo_ixml     = cl_ixml=>create( ).
    lo_sfactory = lo_ixml->create_stream_factory( ).
    lo_istream  = lo_sfactory->create_istream_xstring( string = lv_xml_raw ).
    lo_document = lo_ixml->create_document( ).
    lo_parser   = lo_ixml->create_parser( stream_factory = lo_sfactory
                                          istream        = lo_istream
                                          document       = lo_document ).
    " ── 3. 파싱 실행 ──────────────────────────────────────────────────
    lv_rc = lo_parser->parse( ).
    IF lv_rc <> 0.
      WRITE: / '파싱 오류 발생'.
      RETURN.
    ENDIF.

    3-2. getElementsByTagName — 태그별 속성 추출

    DATA: lo_elements TYPE REF TO if_ixml_node_list,
          lo_element  TYPE REF TO if_ixml_element,
          lo_node     TYPE REF TO if_ixml_node,
          lv_id       TYPE string,
          lv_status   TYPE string,
          lv_amount   TYPE string,
          lv_idx      TYPE i.
    
    " <order> 태그 전체 수집
    lo_elements = lo_document->get_elements_by_tag_name( 'order' ).
    
    lv_idx = 0.
    WHILE lv_idx < lo_elements->get_length( ).
      lo_node    = lo_elements->get_item( lv_idx ).
      lo_element ?= lo_node.
    
      " 속성값 추출: get_attribute( )
      lv_id     = lo_element->get_attribute( 'id' ).
      lv_status = lo_element->get_attribute( 'status' ).
      lv_amount = lo_element->get_attribute( 'amount' ).
    
      WRITE: / |Order id={ lv_id } status={ lv_status } amount={ lv_amount }|.
    
      lv_idx = lv_idx + 1.
    ENDWHILE.
    " → Order id=1001 status=ACTIVE  amount=99.50
    " → Order id=1002 status=CLOSED  amount=150.00

    3-3. 중첩 자식 노드 속성 추출

    DATA: lo_child    TYPE REF TO if_ixml_node,
          lo_cust_el  TYPE REF TO if_ixml_element,
          lv_cid      TYPE string,
          lv_cname    TYPE string.
    
    lv_idx = 0.
    WHILE lv_idx < lo_elements->get_length( ).
      lo_node    = lo_elements->get_item( lv_idx ).
      lo_element ?= lo_node.
    
      lv_id = lo_element->get_attribute( 'id' ).
    
      " 자식 노드 순회 → <customer> 찾기
      lo_child = lo_element->get_first_child( ).
      WHILE lo_child IS BOUND.
        IF lo_child->get_type( ) = if_ixml_node=>co_node_element.
          lo_cust_el ?= lo_child.
          IF lo_cust_el->get_name( ) = 'customer'.
            lv_cid   = lo_cust_el->get_attribute( 'cid' ).
            lv_cname = lo_cust_el->get_attribute( 'name' ).
            WRITE: / |  Order { lv_id } → 고객 { lv_cid }: { lv_cname }|.
          ENDIF.
        ENDIF.
        lo_child = lo_child->get_next( ).
      ENDWHILE.
    
      lv_idx = lv_idx + 1.
    ENDWHILE.

    3-4. 전체 속성 열거 (속성명 모를 때)

    DATA: lo_attrs   TYPE REF TO if_ixml_named_node_map,
          lo_attr    TYPE REF TO if_ixml_node,
          lv_aname   TYPE string,
          lv_avalue  TYPE string,
          lv_aidx    TYPE i,
          lv_alen    TYPE i.
    
    lo_elements = lo_document->get_elements_by_tag_name( 'order' ).
    lo_node     = lo_elements->get_item( 0 ).
    lo_element ?= lo_node.
    
    " 모든 속성 맵 가져오기
    lo_attrs = lo_element->get_attributes( ).
    lv_alen  = lo_attrs->get_length( ).
    
    lv_aidx = 0.
    WHILE lv_aidx < lv_alen.
      lo_attr  = lo_attrs->get_item( lv_aidx ).
      lv_aname  = lo_attr->get_name( ).
      lv_avalue = lo_attr->get_value( ).
      WRITE: / |  속성: { lv_aname } = { lv_avalue }|.
      lv_aidx = lv_aidx + 1.
    ENDWHILE.
    " → 속성: id     = 1001
    " → 속성: status = ACTIVE
    " → 속성: amount = 99.50

    4. 문자열 파싱 FIND / SPLIT 조합

    외부 라이브러리 없이 FIND, SPLIT, SUBSTRING만으로 고정 포맷 텍스트에서 속성값을 추출하는 방법이다.

    4-1. FIND + 오프셋·길이 활용

    DATA: lv_text    TYPE string,
          lv_value   TYPE string,
          lv_offset  TYPE i,
          lv_length  TYPE i,
          lv_start   TYPE i.
    
    lv_text = '<meta name="description" content="SAP ABAP 개발 가이드" charset="UTF-8">'.
    
    " ── content 속성값 추출 ───────────────────────────────────────────
    " 1단계: 'content="' 위치 탐색
    FIND 'content="' IN lv_text MATCH OFFSET lv_offset MATCH LENGTH lv_length.
    
    IF sy-subrc = 0.
      " 2단계: 속성값 시작 위치 = content=" 끝 바로 다음
      lv_start = lv_offset + lv_length.
    
      " 3단계: 닫는 따옴표까지 길이 계산
      FIND '"' IN SECTION OFFSET lv_start OF lv_text
        MATCH OFFSET lv_offset.
    
      " 4단계: 잘라내기
      lv_length = lv_offset - lv_start.
      lv_value  = lv_text+lv_start(lv_length).
    
      WRITE: / |content = { lv_value }|.   " → SAP ABAP 개발 가이드
    ENDIF.

    4-2. SPLIT을 이용한 속성 토큰 분리

    DATA: lv_tag    TYPE string,
          lt_tokens TYPE TABLE OF string,
          lv_token  TYPE string,
          lt_kv     TYPE TABLE OF string,
          lv_key    TYPE string,
          lv_val    TYPE string.
    
    " <...> 안의 내용만 잘라내기
    lv_tag = '<input type="text" id="userId" value="admin" disabled>'.
    FIND REGEX '<([^>]+)>' IN lv_tag SUBMATCHES lv_tag.
    
    " 공백으로 토큰 분리 (첫 번째 = 태그명)
    SPLIT lv_tag AT ' ' INTO TABLE lt_tokens.
    
    LOOP AT lt_tokens INTO lv_token.
      " 값 없는 속성(disabled 등) 제외
      IF lv_token CS '='.
        " = 기준 분리
        SPLIT lv_token AT '=' INTO lv_key lv_val.
        " 따옴표 제거
        REPLACE ALL OCCURRENCES OF '"'  IN lv_val WITH ''.
        REPLACE ALL OCCURRENCES OF ''''  IN lv_val WITH ''.
        CONDENSE lv_key NO-GAPS.
        WRITE: / |  { lv_key } → { lv_val }|.
      ENDIF.
    ENDLOOP.
    " →  type  → text
    " →  id    → userId
    " →  value → admin

    5. 유틸리티 클래스 재사용 가능한 캡슐화

    위의 패턴을 lcl_attr_extractor 로컬 클래스로 묶어 어디서나 간편하게 호출할 수 있도록 한다.

    5-1. 클래스 정의

    CLASS lcl_attr_extractor DEFINITION.
      PUBLIC SECTION.
    
        TYPES: BEGIN OF ty_attr,
                 name  TYPE string,
                 value TYPE string,
               END OF ty_attr.
        TYPES: tt_attr TYPE STANDARD TABLE OF ty_attr WITH DEFAULT KEY.
    
        " 특정 속성 하나 추출 (REGEX)
        CLASS-METHODS get_attr
          IMPORTING iv_html      TYPE string
                    iv_attr_name TYPE string
          RETURNING VALUE(rv_value) TYPE string.
    
        " 특정 태그의 모든 속성 추출 (첫 번째 매칭 태그)
        CLASS-METHODS get_all_attrs
          IMPORTING iv_html     TYPE string
                    iv_tag_name TYPE string
          RETURNING VALUE(rt_attrs) TYPE tt_attr.
    
        " 특정 태그의 반복 속성값을 테이블로 수집
        CLASS-METHODS collect_attr
          IMPORTING iv_html      TYPE string
                    iv_tag_name  TYPE string
                    iv_attr_name TYPE string
          RETURNING VALUE(rt_values) TYPE string_table.
    
    ENDCLASS.
    
    CLASS lcl_attr_extractor IMPLEMENTATION.
    
      METHOD get_attr.
        " 패턴: attr_name\s*=\s*["']([^"']*)["']
        DATA lv_pattern TYPE string.
        lv_pattern = iv_attr_name && '\s*=\s*["\x27]([^"\x27]*)["\x27]'.
        FIND REGEX lv_pattern IN iv_html SUBMATCHES rv_value.
      ENDMETHOD.
    
      METHOD get_all_attrs.
        " 1. 해당 태그 전체 텍스트 추출
        DATA: lv_tag_text TYPE string,
              lo_regex    TYPE REF TO cl_abap_regex,
              lo_matcher  TYPE REF TO cl_abap_matcher,
              ls_attr     TYPE ty_attr,
              lv_pattern  TYPE string.
    
        lv_pattern = '<' && iv_tag_name && '\s([^>]*)'.
        FIND REGEX lv_pattern IN iv_html SUBMATCHES lv_tag_text.
        CHECK sy-subrc = 0.
    
        " 2. key=value 쌍 반복 추출
        lo_regex   = cl_abap_regex=>create(
                       pattern     = '(\w[\w\-]*)\s*=\s*["\x27]([^"\x27]*)["\x27]'
                       ignore_case = abap_true ).
        lo_matcher = lo_regex->create_matcher( text = lv_tag_text ).
    
        WHILE lo_matcher->find_next( ) = abap_true.
          ls_attr-name  = lo_matcher->get_submatch( 1 ).
          ls_attr-value = lo_matcher->get_submatch( 2 ).
          APPEND ls_attr TO rt_attrs.
        ENDWHILE.
      ENDMETHOD.
    
      METHOD collect_attr.
        " 패턴: <tag_name[^>]*attr_name="value"
        DATA: lv_pattern  TYPE string,
              lo_regex    TYPE REF TO cl_abap_regex,
              lo_matcher  TYPE REF TO cl_abap_matcher,
              lv_value    TYPE string.
    
        lv_pattern = '<' && iv_tag_name
                  && '[^>]*' && iv_attr_name
                  && '\s*=\s*["\x27]([^"\x27]*)["\x27]'.
    
        lo_regex   = cl_abap_regex=>create( pattern = lv_pattern
                                            ignore_case = abap_true ).
        lo_matcher = lo_regex->create_matcher( text = iv_html ).
    
        WHILE lo_matcher->find_next( ) = abap_true.
          lv_value = lo_matcher->get_submatch( 1 ).
          APPEND lv_value TO rt_values.
        ENDWHILE.
      ENDMETHOD.
    
    ENDCLASS.

    5-2. 호출 예시

    DATA: lv_html   TYPE string,
          lv_val    TYPE string,
          lt_attrs  TYPE lcl_attr_extractor=>tt_attr,
          lt_srcs   TYPE string_table,
          ls_attr   TYPE lcl_attr_extractor=>ty_attr,
          lv_src    TYPE string.
    
    lv_html =
      '<form action="/submit" method="POST" id="loginForm">'
      && '<input type="text"     name="userId"   value="admin"/>'
      && '<input type="password" name="password" value=""/>'
      && '<img src="logo.png"  alt="로고"/>'
      && '<img src="banner.jpg" alt="배너"/>'
      && '</form>'.
    
    " ① 단일 속성값 추출
    lv_val = lcl_attr_extractor=>get_attr(
               iv_html      = lv_html
               iv_attr_name = 'action' ).
    WRITE: / |form action = { lv_val }|.    " → /submit
    
    " ② <form> 태그 전체 속성 목록
    lt_attrs = lcl_attr_extractor=>get_all_attrs(
                 iv_html     = lv_html
                 iv_tag_name = 'form' ).
    LOOP AT lt_attrs INTO ls_attr.
      WRITE: / |  { ls_attr-name } = { ls_attr-value }|.
    ENDLOOP.
    " → action = /submit
    " → method = POST
    " → id     = loginForm
    
    " ③ <img> 태그 src 전체 수집
    lt_srcs = lcl_attr_extractor=>collect_attr(
                iv_html      = lv_html
                iv_tag_name  = 'img'
                iv_attr_name = 'src' ).
    LOOP AT lt_srcs INTO lv_src.
      WRITE: / |img src = { lv_src }|.
    ENDLOOP.
    " → logo.png
    " → banner.jpg

    ⚠️ HTML 인코딩 주의: 속성값에 &amp;, &lt; 등 HTML 엔티티가 포함된 경우 추출 후 별도로 디코딩 처리가 필요하다. REPLACE ALL OCCURRENCES OF '&amp;' IN lv_val WITH '&' 형태로 처리한다.

    6. 패턴 요약 표

    목적 핵심 코드 비고
    단일 속성 추출 (REGEX) FIND REGEX 'attr\s*=\s*["\x27]([^"\x27]*)["\x27]'
    IN lv_html SUBMATCHES lv_val.
    가장 간단
    전체 반복 수집 FIND ALL OCCURRENCES OF REGEX …
    IN lv_html RESULTS lt_match.
    복수 태그
    OOP REGEX cl_abap_regex=>create( pattern = … )
    lo_matcher->find_next( )
    lo_matcher->get_submatch( 1 )
    루프 매칭
    XML DOM (표준) cl_ixml + get_elements_by_tag_name
    lo_element->get_attribute( 'attr' )
    Well-formed XML
    전체 속성 열거 lo_element->get_attributes( )
    lo_attrs->get_item( idx )->get_name/value
    속성명 미정 시
    문자열 파싱 FIND 'attr="' MATCH OFFSET …
    lv_text+start(length)
    고정 포맷
    자주 쓰는 REGEX 패턴 설명
    attr\s*=\s*["\x27]([^"\x27]*)["\x27] 속성값 추출 (큰따옴표·작은따옴표 모두)
    <tagname[^>]*> 특정 태그 전체 매칭
    (\w[\w\-]*)\s*=\s*["\x27]([^"\x27]*)["\x27] key=value 쌍 전체 수집
    <tagname[^>]*attr="([^"]*)" 특정 태그의 특정 속성 조합
    #SAP_ABAP #HTML_파싱 #XML_파싱 #REGEX #cl_ixml #cl_abap_regex #FIND_ALL_OCCURRENCES #속성값_추출 #DOM
Designed by Tistory.