관련지식
javascript, jquery, DataTransfer, onpaste

드문 경우지만 타사이트에 있는 항목을 복사해오는 기능이 필요할 수 있습니다. 타사이트의 화면 영역을 선택해서 특정 영역에 붙여넣기를 하면 복사했던 내용을 분석해서 필요한 값만 추출하는 것이죠. 어려우신가요?
혹시 배송대행 서비스를 하는 이하넥스를 이용해본적이 있다면 이해하기 쉬울것입니다. 아마존이나 이베이에서 주문한 내역을 이하넥스에 동일하게 입력해야 하는데 상품명/가격/수량 등등을 수동으로 입력하기엔 불편하죠. 그래서 해당 쇼핑몰의 주문내역 영역 전체를 복사/붙여넣기하면 각 항목이 자동으로 입력되도록 구현되어 있습니다. 그래서 붙여넣기 할 영역이 아래처럼 큼지막하게 만들어놨네요.

또는 구글의 스프레드에서도 비슷한 기능을 볼수 있습니다. 웹사이트의 특정 영역을 복사/붙여넣기 하면 한개의 셀에 복사되지 않고 어떤 규칙에 의해 여러 셀에 복사되죠. 이것도 비슷한 형태의 코딩을 한것입니다.

그래서 쿠팡의 주문내역 화면에서 상품명, 금액, 수량을 복사하는 기능을 만들어보겠습니다. 파싱한 데이터를 단순히 console.log로 출력할 것이지만 활용 방법은 충분히 상상이 되실것 같습니다. 사용할 HTML은 아래와 같고 최종 소스는 맨 아래에 있습니다.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  7. <style>
  8. div { width: 700px; height:200px; border:1px solid black; overflow: hidden; }
  9. </style>
  10. </head>
  11. <body>
  12. <div id="div1">텍스트 분석 방식</div>
  13. <div id="div2" contenteditable="true">HTML 분석 방식</div>
  14. <script>
  15. </script>
  16. </body>
  17. </html>

쿠팡 주문내역에선 아래와 같이 선택해서 복사하면 됩니다. 가정주부 같아서 부끄럽네요

방법

가장 핵심은 타사이트에서 복사한 내용을 어떻게 가져오느냐 하는것입니다. 어차피 자동으로 붙여넣기는 할수 없기 때문에 특정 영역에 사용자가 직접 붙여넣기를 해줘야 합니다. 하지만 그때 두가지 방법이 발생합니다.

  1. 완전히 붙여넣어진 값을 읽어서 분석한다.
  2. 클립보드로부터 값을 읽어들인다.

브라우저 호환성에선 1번 방법이 가장 좋겠지만 원본 데이터에 가장 근접한것은 2번입니다. 따라서 가급적 2번을 통해서 데이터를 가져오고 그것이 여의치 않으면 1번을 통해서 가져오는 것이 좋을 것입니다.

텍스트 파싱

먼저 텍스트를 분석하여 값을 추출하는 방법을 알려드리겠습니다. 먼저 paste 이벤트가 발생할수 있는 영역을 만들어야 합니다. 위 샘플에선 div 영역을 만들었지만 하위브라우저에서는 입력 가능한 영역이 아니면 paste 이벤트가 발생하지 않습니다. 일단 크롬에선 문제가 없는 샘플입니다.

아래와 같이 div1에 paste 이벤트를 등록하겠습니다.

  1. $('#div1').on('paste', function() {
  2. });

Ctrl+v 등으로 붙여넣기 이벤트가 발생하면 이벤트를 통해 클립보드 데이터를 불러올수 있습니다.

  1. var pasteObj = (event.clipboardData || window.clipboardData);
  2. var texts = pasteObj.getData('text').split('\n');

pasteObj 는 DataTransfer 객체입니다. getData('text')를 하게 되면 클립보드에 저장된 내용중 텍스트 부분만 가져올수 있습니다. 어떤 웹화면의 일부를 복사하면 HTML의 DOM정보도 같이 복사되지만 getData('text') 는 DOM정보를 제외한 textContent에 해당하는 부분만 반환하게 되죠.

참고 : https://developer.mozilla.org/ko/docs/Web/API/DataTransfer

getData('text')를 통해 가져온 데이터는 아래와 같습니다.

주문내역에 있던 텍스트 부분이 보이실겁니다. 저 텍스트의 규칙을 분석하여 상품명, 금액, 수량을 분석하면 됩니다. 간단하죠?

  1. for(var i = 0; i < texts.length; i++) {
  2. var text = texts[i];
  3. if(!text.match(/^[0-9,]+원/g)) {
  4. continue;
  5. }
  6. var goods = texts[i - 1];
  7. goods = goods.substring(0, goods.length / 2);
  8. var vals = text.split('/');
  9. console.log(vals[0], vals[1], goods);
  10. }

이 방법은 매우 간편하지만 매우 취약합니다. 화면 컨텐츠에서 텍스트가 추가/삭제되거나 위치가 바뀌는것은 매우 흔한 일 이니까요. 이것보다는 HTML DOM을 파싱하는 것이 좋을것 입니다.

HTML 파싱

HTML파싱을 위한 div는 contenteditable="true" 속성을 주었습니다. div는 원래 편집가능한 요소가 아니지만 저 속성으로 인해 편집이 가능한 속성이 됩니다. 붙여넣기 이벤트가 발생하는것으로 끝나지 않고 실제로 HTML요소에 붙여넣기가 되죠.

텍스트 파싱할때와 마찬가지로 paste 이벤트를 등록합니다.

  1. $('#div2').on('paste', function() {
  2. var $this = $(this);
  3. $this.text('');
  4. var pasteObj = (event.clipboardData || window.clipboardData);
  5. try {
  6. parse(pasteObj.getData('text/html'));
  7. }
  8. catch(e) {
  9. setTimeout(function() {
  10. parse($this.html());
  11. }, 100);
  12. }
  13. });

클립보드로 부터 데이터를 가져오는 부분은 동일하지만 파라미터 값으로 'text/html'을 주었습니다. 그래야만 HTML 데이터를 가져올수 있습니다. 하지만 익스플로러11 같은 낮은 버전의 브라우저에서는 pasteObj.getData('text/html') 을 사용할수 없습니다. 에러가 발생하죠. 그 경우엔 DOM에서 가져오도록 예외처리 했습니다. paste 이벤트가 발생한 시점에 div 요소에 복사가 됐음을 보장 못하므로 약간의 유예 시간이 필요합니다. 그것을 위한 100밀리초 지만 환경에 따라 시간을 조정해야 할수도 있습니다.

어쨌든 HTML을 가져오게 되면 그 다음부턴 간단합니다. 태그, 클래스 등으로 구조적으로 필요한 데이터를 가져올수 있습니다. 문자열을 파싱하는것보다 복잡해 보이지만 유지보수 측면에선 이편이 훨씬 낫습니다.

  1. function parse(html) {
  2. var items = $(html).find('.my-order-unit__item-info');
  3. items.each(function() {
  4. var $this = $(this);
  5. var goods = $this.find('strong:not(.my-color--refurbished)').text();
  6. var vals = $this.find('.my-order-unit__info-ea ').text().split('/');
  7. console.log(vals[0], vals[1], goods);
  8. });
  9. }

정리

이 방법은 일반적으로 사용되진 않겠지만 어떤 특수한 경우엔 매우 유용할수 있습니다. paste 이벤트, DataTransfer 두 가지만 알고있으면 필요할때 이용할수 있겠네요.

1번 방식은 편집 불가능한 div에 paste 이벤트를 등록했는데, 이것은 익스플로러에선 이벤트가 발생하지 않을수 있습니다. 그경우 편집 가능하도록 속성을 추가하거나 inputtextarea 등의 요소를 이용하면 됩니다.

최종 샘플

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  7. <style>
  8. div { width: 700px; height:200px; border:1px solid black; overflow: hidden; }
  9. </style>
  10. </head>
  11. <body>
  12. <div id="div1">텍스트 분석 방식</div>
  13. <div id="div2" contenteditable="true">HTML 분석 방식</div>
  14. <script>
  15. $('#div1').on('paste', function() {
  16. var pasteObj = (event.clipboardData || window.clipboardData);
  17. var texts = pasteObj.getData('text').split('\n');
  18. console.log(pasteObj.getData('text'));
  19. for(var i = 0; i < texts.length; i++) {
  20. var text = texts[i];
  21. if(!text.match(/^[0-9,]+원/g)) {
  22. continue;
  23. }
  24. var goods = texts[i - 1];
  25. goods = goods.substring(0, goods.length / 2);
  26. var vals = text.split('/');
  27. console.log(vals[0], vals[1], goods);
  28. }
  29. });
  30. $('#div2').on('paste', function() {
  31. var $this = $(this);
  32. $this.text('');
  33. var pasteObj = (event.clipboardData || window.clipboardData);
  34. try {
  35. parse(pasteObj.getData('text/html'));
  36. }
  37. catch(e) {
  38. setTimeout(function() {
  39. parse($this.html());
  40. }, 100);
  41. }
  42. });
  43. function parse(html) {
  44. var items = $(html).find('.my-order-unit__item-info');
  45. items.each(function() {
  46. var $this = $(this);
  47. var goods = $this.find('strong:not(.my-color--refurbished)').text();
  48. var vals = $this.find('.my-order-unit__info-ea ').text().split('/');
  49. console.log(vals[0], vals[1], goods);
  50. });
  51. }
  52. </script>
  53. </body>
  54. </html>