레이블이 difflib인 게시물을 표시합니다. 모든 게시물 표시
레이블이 difflib인 게시물을 표시합니다. 모든 게시물 표시

2022년 5월 5일 목요일

Python 문장(문자열) 의 비교, diff 생성,patch 생성, beyond compare 처럼 구현하기(기본예제)

앞에서 복잡한 예제를 만들었었는데 여기에서는 보다 단순한 사용법 예제를 만들어 봤습니다.

예제가 되는 기본은 아래 링크를 참고 하였습니다.

https://docs.python.org/ko/3/library/difflib.html 


여러종류의 함수들이 있는데 각각 동작상 차이가 많이 있기 때문에

예제코드 아래쪽에는 각각의 실행 결과도 주석으로 포함하였습니다.

비교시 가능하면 list 형태로 인자를 넘겨주며 list형태로 만들기 위해서 splitlines함수를 이용하였습니다. 그리고 다시 화면 출력을 위해서 list join을 해야하는데 개행이 splitlines하면서 사라졌기 때문에 join시 개행을 붙였습니다.

ndiff,  Differ.compare() 는 거의 비슷한 출력을 보여주며 전체 라인에 달라지거나 사라진 정보등등 표현이 됩니다.

unified_diff는 ndiff와 비슷하긴 하나 전체 목록을 보여주지는 않고 달라진점 위 아래로 정보가 나오게 됩니다.

context_diff도 달라진 부분만 나오긴하나 좀더 구체적인 항목은 나오지 않고 다른 라인 정보만 나타납니다.

HtmlDiff()는 html 형태로 출력이 됩니다.

import difflib
import sys
from pprint import pprint
text1 = "  1. Beautiful is \n  2. better \n  3. than ugly. \n  4. Explicit is better than implicit.\n  5. Simple is better than complex.\n  6. Complex is better than complicated.\n"
text2 = "  1. Beautiful is \n  2. better \n  3. than ugly. \n  4. Explicit is better than implicit.\n  5. Simple is betr than complex.\n  6. Comaaaplex is better than compddlicated.\n"
text1 = text1.splitlines()
text2 = text2.splitlines()
d = difflib.Differ()
result = list(d.compare(text1, text2))
print("\n".join(result)) # 모든 리스트를 합칩니다.리스트를 합칠때 개행을 입력합니다.
'''
    1. Beautiful is
    2. better
    3. than ugly.
    4. Explicit is better than implicit.
-   5. Simple is better than complex.
?                   --

+   5. Simple is betr than complex.
-   6. Complex is better than complicated.
+   6. Comaaaplex is better than compddlicated.
?         +++                        ++
'''


print("ndiff")
'''a와 b(문자열의 리스트)를 비교합니다; Differ-스타일 델타(델타 줄을 생성하는 제너레이터)를 반환합니다.'''
result = list(difflib.ndiff(text1, text2))
print("\n".join(result)) 
'''
ndiff
    1. Beautiful is
    2. better
    3. than ugly.
    4. Explicit is better than implicit.
-   5. Simple is better than complex.
?                   --

+   5. Simple is betr than complex.
-   6. Complex is better than complicated.
+   6. Comaaaplex is better than compddlicated.
?         +++                        ++
'''


print("unified_diff")
'''
a와 b(문자열의 리스트)를 비교합니다; 델타(델타 줄을 생성하는 제너레이터)를 통합 diff 형식으로 반환합니다.
통합(unified) diff는 단지 변경된 줄과 몇 줄의 문맥만을 더해서 표시하는 간결한 방법입니다. 
변경 사항은 (별도의 이전/이후 블록 대신) 인라인 스타일로 표시됩니다. 문맥 줄의 수는 n에 의해 설정되며 기본값은 3입니다.
'''
result = list(difflib.unified_diff(text1, text2))
print("\n".join(result)) 
'''
unified_diff
---

+++

@@ -2,5 +2,5 @@

   2. better
   3. than ugly.
   4. Explicit is better than implicit.
-  5. Simple is better than complex.
-  6. Complex is better than complicated.
+  5. Simple is betr than complex.
+  6. Comaaaplex is better than compddlicated.
'''



print("context_diff")
result = list(difflib.context_diff(text1, text2))
print("\n".join(result)) 
'''
context_diff
***

---

***************

*** 2,6 ****

    2. better
    3. than ugly.
    4. Explicit is better than implicit.
!   5. Simple is better than complex.
!   6. Complex is better than complicated.
--- 2,6 ----

    2. better
    3. than ugly.
    4. Explicit is better than implicit.
!   5. Simple is betr than complex.
!   6. Comaaaplex is better than compddlicated.
'''

print("HtmlDiff")
result = list(difflib.HtmlDiff().make_file(text1, text2))
print("".join(result)) 
'''
HtmlDiff

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>
    <meta http-equiv="Content-Type"
          content="text/html; charset=utf-8" />
    <title></title>
    <style type="text/css">
        table.diff {font-family:Courier; border:medium;}
        .diff_header {background-color:#e0e0e0}
        td.diff_header {text-align:right}
        .diff_next {background-color:#c0c0c0}
        .diff_add {background-color:#aaffaa}
        .diff_chg {background-color:#ffff77}
        .diff_sub {background-color:#ffaaaa}
    </style>
</head>

<body>

    <table class="diff" id="difflib_chg_to0__top"
           cellspacing="0" cellpadding="0" rules="groups" >
        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>
        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>

        <tbody>
            <tr><td class="diff_next" id="difflib_chg_to0__0"><a href="#difflib_chg_to0__0">f</a></td><td class="diff_header" id="from0_1">1</td><td nowrap="nowrap">&nbsp;&nbsp;1.&nbsp;Beautiful&nbsp;is&nbsp;</td><td class="diff_next"><a href="#difflib_chg_to0__0">f</a></td><td class="diff_header" id="to0_1">1</td><td nowrap="nowrap">&nbsp;&nbsp;1.&nbsp;Beautiful&nbsp;is&nbsp;</td></tr>
            <tr><td class="diff_next"></td><td class="diff_header" id="from0_2">2</td><td nowrap="nowrap">&nbsp;&nbsp;2.&nbsp;better&nbsp;</td><td class="diff_next"></td><td class="diff_header" id="to0_2">2</td><td nowrap="nowrap">&nbsp;&nbsp;2.&nbsp;better&nbsp;</td></tr>
            <tr><td class="diff_next"></td><td class="diff_header" id="from0_3">3</td><td nowrap="nowrap">&nbsp;&nbsp;3.&nbsp;than&nbsp;ugly.&nbsp;</td><td class="diff_next"></td><td class="diff_header" id="to0_3">3</td><td nowrap="nowrap">&nbsp;&nbsp;3.&nbsp;than&nbsp;ugly.&nbsp;</td></tr>
            <tr><td class="diff_next"></td><td class="diff_header" id="from0_4">4</td><td nowrap="nowrap">&nbsp;&nbsp;4.&nbsp;Explicit&nbsp;is&nbsp;better&nbsp;than&nbsp;implicit.</td><td class="diff_next"></td><td class="diff_header" id="to0_4">4</td><td nowrap="nowrap">&nbsp;&nbsp;4.&nbsp;Explicit&nbsp;is&nbsp;better&nbsp;than&nbsp;implicit.</td></tr>
            <tr><td class="diff_next"><a href="#difflib_chg_to0__top">t</a></td><td class="diff_header" id="from0_5">5</td><td nowrap="nowrap">&nbsp;&nbsp;5.&nbsp;Simple&nbsp;is&nbsp;bet<span class="diff_sub">te</span>r&nbsp;than&nbsp;complex.</td><td class="diff_next"><a href="#difflib_chg_to0__top">t</a></td><td class="diff_header" id="to0_5">5</td><td nowrap="nowrap">&nbsp;&nbsp;5.&nbsp;Simple&nbsp;is&nbsp;betr&nbsp;than&nbsp;complex.</td></tr>
            <tr><td class="diff_next"></td><td class="diff_header" id="from0_6">6</td><td nowrap="nowrap">&nbsp;&nbsp;6.&nbsp;Complex&nbsp;is&nbsp;better&nbsp;than&nbsp;complicated.</td><td class="diff_next"></td><td class="diff_header" id="to0_6">6</td><td nowrap="nowrap">&nbsp;&nbsp;6.&nbsp;Com<span class="diff_add">aaa</span>plex&nbsp;is&nbsp;better&nbsp;than&nbsp;comp<span class="diff_add">dd</span>licated.</td></tr>
        </tbody>
    </table>
    <table class="diff" summary="Legends">
        <tr> <th colspan="2"> Legends </th> </tr>
        <tr> <td> <table border="" summary="Colors">
                      <tr><th> Colors </th> </tr>
                      <tr><td class="diff_add">&nbsp;Added&nbsp;</td></tr>
                      <tr><td class="diff_chg">Changed</td> </tr>
                      <tr><td class="diff_sub">Deleted</td> </tr>
                  </table></td>
             <td> <table border="" summary="Links">
                      <tr><th colspan="2"> Links </th> </tr>
                      <tr><td>(f)irst change</td> </tr>
                      <tr><td>(n)ext change</td> </tr>
                      <tr><td>(t)op</td> </tr>
                  </table></td> </tr>
    </table>
</body>

</html>
'''



2021년 11월 12일 금요일

Python 문장(문자열)의 비교, diff 생성,patch 생성, beyond compare 처럼 구현하기(실전 예제)

앞에서 간단하게 SequenceMatcher의 함수에 대해서 살펴 보았습니다.

https://swlock.blogspot.com/2021/11/python-diff-patch-beyond-compare.html

이번에는 좀 더 실전적인 예제를 통해서 쓸만한 함수를 살펴 보도록 하겠습니다.

어떤 주어진 데이터의 비교 결과를 colorful 하게 표시하는게 이번 시간의 목표입니다. 칼라로 표시하려면 여러가지 방식이 있겠지만 가장 간단한 방법은 html tag로 표시해서 브라우저로 보는 방법입니다.


목표

브라우저로 결과물을 열었을때 다음과 같이 다른 부분만 색상이 다르게 나오도록 작업합니다.




전체 코드

from difflib import SequenceMatcher

def get_diff_colored(string1,string2,diff_color):
	reta = ""
	retb = ""
	
	s = SequenceMatcher(None,string1,string2)
	
	for tag,i1,i2,j1,j2 in s.get_opcodes():
		if tag=='equal':
			reta += string1[i1:i2]
			retb += string2[j1:j2]
		else:
			reta += '<span style="color:rgb('+diff_color+');">'+string1[i1:i2]+'</span>'
			retb += '<span style="color:rgb('+diff_color+');">'+string2[j1:j2]+'</span>'
			
	return [reta,retb]

def change_dspace_to_space(string_data):
	while True:
		slen = len(string_data)
		string_data = string_data.replace("  "," ")
		if len(string_data)==slen:
			return string_data

if __name__ == '__main__':
	ret = get_diff_colored("    Hallo   God    Monin  123,  12312, 123,   123,  12312","Hello Good Morning 123 12312,123,123,2312","255,0,0")
	print("<html><body>")
	print(ret[0])
	print("<BR>")
	print(ret[1])
	ret = get_diff_colored(change_dspace_to_space("    Hallo   God    Monin  123,  12312, 123,   123,  12312"),change_dspace_to_space("Hello Good Morning 123 12312,123,123,2312"),"255,0,0")
	print("<BR>")
	print("<BR>")
	print(ret[0])
	print("<BR>")
	print(ret[1])
	print("</body></html>")

실제 화면 출력

python sequencemacher_test.py 실행시키면 아래와 같은 형태로 나옵니다.

<html><body>
<span style="color:rgb(255,0,0);">    </span>H<span style="color:rgb(255,0,0);">a</span>llo <span style="color:rgb(255,0,0);">  </span>G<span style="color:rgb(255,0,0);"></span>od <span style="color:rgb(255,0,0);">   </span>Mo<span style="color:rgb(255,0,0);"></span>nin<span style="color:rgb(255,0,0);"> </span> 123<span style="color:rgb(255,0,0);">, </span> 12312,<span style="color:rgb(255,0,0);"> </span>123,<span style="color:rgb(255,0,0);">   </span>123,<span style="color:rgb(255,0,0);">  1</span>2312
<BR>
<span style="color:rgb(255,0,0);"></span>H<span style="color:rgb(255,0,0);">e</span>llo <span style="color:rgb(255,0,0);"></span>G<span style="color:rgb(255,0,0);">o</span>od <span style="color:rgb(255,0,0);"></span>Mo<span style="color:rgb(255,0,0);">r</span>nin<span style="color:rgb(255,0,0);">g</span> 123<span style="color:rgb(255,0,0);"></span> 12312,<span style="color:rgb(255,0,0);"></span>123,<span style="color:rgb(255,0,0);"></span>123,<span style="color:rgb(255,0,0);"></span>2312
<BR>
<BR>
<span style="color:rgb(255,0,0);"> </span>H<span style="color:rgb(255,0,0);">a</span>llo Go<span style="color:rgb(255,0,0);"></span>d Mo<span style="color:rgb(255,0,0);"></span>nin<span style="color:rgb(255,0,0);"></span> 123<span style="color:rgb(255,0,0);">,</span> 12312,<span style="color:rgb(255,0,0);"> </span>123,<span style="color:rgb(255,0,0);"> </span>123,<span style="color:rgb(255,0,0);"> 1</span>2312
<BR>
<span style="color:rgb(255,0,0);"></span>H<span style="color:rgb(255,0,0);">e</span>llo Go<span style="color:rgb(255,0,0);">o</span>d Mo<span style="color:rgb(255,0,0);">r</span>nin<span style="color:rgb(255,0,0);">g</span> 123<span style="color:rgb(255,0,0);"></span> 12312,<span style="color:rgb(255,0,0);"></span>123,<span style="color:rgb(255,0,0);"></span>123,<span style="color:rgb(255,0,0);"></span>2312
</body></html>

위와 같은 콘솔 출력 상태는 브라우저에서 볼 수가 없으므로 파일로 저장이 필요합니다.

그럼 결과를 파일로 저장하는 ">" 리다이렉션을 사용합니다.

python sequencemacher_test.py > 1.html

1.html 을 브라우저로 열도록 합니다.


코드 설명

SequenceMatcher 의 get_opcodes 함수를 사용하였습니다. 이 함수는 다른부분 삭제된 부분 그런 자세한 위치를 알려주는 함수인데 같은 부분만 사용하고 나머지는 다르다고 판단하고 색상을 넣었습니다. 색상을 넣을때는 html span style tag를 사용하였습니다.

그리고 간혹 white space처리가 제대로 안되는 경우가 있었습니다. white space가 여러개 있는 경우 하나로 변경하는 처리를 따로 구현하였습니다. change_dspace_to_space 함수



2021년 11월 7일 일요일

Python 문장(문자열) 의 비교, diff 생성,patch 생성, beyond compare 처럼 구현하기


문자열 비교 함수는 많이 들어봤을 것입니다. 

python에서는 조건식을 이용하면 일치하는지 쉽게 알 수 있습니다. 아니면 한글자씩 비교하는것도 가능할겁니다.

그런데, 여기에서 구현하고자하는 것은 다른 부분은 색상이 들어오게 하는것입니다. 즉 patch 스타일과 비슷합니다. 물론 최종 목표는 patch형태는 아니지만 전체적인 내용을 확인하는데 도움이 되기 위해서는 patch를 이해하는것이 중요합니다.

patch가 어떤것이냐면,,,, 아래와 같은 형태를 말하는것 입니다. 

git diff로 생성시키면 나오는 patch 파일입니다.

--- a/src/main/java/hello/HelloWorld.java
+++ b/src/main/java/hello/HelloWorld.java
@@ -4,6 +4,7 @@ public class HelloWorld {
     public static void main(String[] args) {
-        String suffix = "@@@";
-        System.out.println(suffix);
+ String prefix = "$$$" + System.out.println(prefix);
} }

이것을 아무런 지식없이 구현하려면 아무래도 신경을 써야 할텐데... python은 기본으로  library에 기능이 있습니다.

여기에서는 단순히 라인에 대해서만 나오고 있지만 실제는 text 문장끼리도 차이점을 알아내도록 준비가 되어있습니다.


difflib 라는 패키지를 소개합니다.

https://docs.python.org/ko/3/library/difflib.html

여기에서는 공식 문서에 기본적인 예제의 내용을 보다 쉽게 설명하고, 다음에 기회가 되면 좀더 실전 예제를 만들어서 소개하도록 하겠습니다.


아래 함수 두개가 patch형태를 만들어 내는 함수입니다.

class difflib.Differ

class difflib.HtmlDiff

사실 이글을 시작하게 되면서 patch를 원했던것은 아니라서 이 부분의 예제의 설명은 건너 뛰도록 하겠습니다.

원하는것은 SequenceMatcher 입니다. 이것은 두개의 문장이나 혹은 두개의 문자열에서 일치하는 부분을 알려주는 class 입니다.

예제:

from difflib import SequenceMatcher

str1="abcde"
str2=" abce"
s = SequenceMatcher(None, str1, str2)
print(s.get_matching_blocks())
for mb in s.get_matching_blocks():
	print(mb, str1[mb.a:mb.a+mb.size])
	print(mb, str2[mb.b:mb.b+mb.size])

첫번째인자는 junk 조건을 넣는데, 기본으로 None 입력 해주면 됩니다.

예제로 주어진 문장은 공백도 있어서 단순히 첫번째 글자와 비교를 하면 처리가 복잡해집니다만, 결과를 보면 아주 만족스럽게 처리를 하고 있습니다. macher 결과는 a , b 두개의 위치 정보가 주어지는데 결국은 아래에서 보듯이 같게 됩니다. ( 일치하는 정보이기 때문 )

결과:

[Match(a=0, b=1, size=3), Match(a=4, b=4, size=1), Match(a=5, b=5, size=0)]
Match(a=0, b=1, size=3) abc
Match(a=0, b=1, size=3) abc
Match(a=4, b=4, size=1) e
Match(a=4, b=4, size=1) e
Match(a=5, b=5, size=0)
Match(a=5, b=5, size=0)


실전 예제

https://swlock.blogspot.com/2021/11/python-diff-patch-beyond-compare_12.html