2015-08-19 2 views
0

Я пробовал все виды логики и методов и даже искал много, но все же не мог придумать удовлетворительного ответа на вопрос, который у меня есть. Я написал программу, как показано ниже, чтобы выделить конкретный xml-код, где я столкнулся с некоторыми проблемами. Извините за то, что этот пост немного длинный. Я только хотел четко объяснить свою проблему.Выделите различия между двумя файлами xml в текстовом поле Tkinter

EDIT: Для запуска ниже данной программы вам потребуется два XML-файлы, которые являются здесь: sample1 и sample2. Сохраните эти файлы и в поле ниже кода отредактируйте местоположение, где вы хотите сохранить ваши файлы в C:/Users/editThisLocation/Desktop/sample1.xml

from lxml import etree 
from collections import defaultdict 
from collections import OrderedDict 
from distutils.filelist import findall 
from lxml._elementpath import findtext 

from Tkinter import * 
import Tkinter as tk 
import ttk 

root = Tk() 

class CustomText(tk.Text): 

    def __init__(self, *args, **kwargs): 
     tk.Text.__init__(self, *args, **kwargs) 


    def highlight_pattern(self, pattern, tag, start, end, 
          regexp=True): 

     start = self.index(start) 
     end = self.index(end) 
     self.mark_set("matchStart", start) 
     self.mark_set("matchEnd", start) 
     self.mark_set("searchLimit", end) 

     count = tk.IntVar() 
     while True: 
      index = self.search(pattern, "matchEnd","searchLimit", 
           count=count, regexp=regexp) 
      if index == "": break 
      self.mark_set("matchStart", index) 
      self.mark_set("matchEnd", "%s+%sc" % (index, count.get())) 
      self.tag_add(tag, "matchStart", "matchEnd") 

    def Remove_pattern(self, pattern, tag, start="1.0", end="end", 
          regexp=True): 

     start = self.index(start) 
     end = self.index(end) 
     self.mark_set("matchStart", start) 
     self.mark_set("matchEnd", start) 
     self.mark_set("searchLimit", end) 

     count = tk.IntVar() 
     while True: 
      index = self.search(pattern, "matchEnd","searchLimit", 
           count=count, regexp=regexp) 
      if index == "": break 
      self.mark_set("matchStart", index) 
      self.mark_set("matchEnd", "%s+%sc" % (index, count.get())) 
      self.tag_remove(tag, start, end) 



recovering_parser = etree.XMLParser(recover=True) 


sample1File = open('C:/Users/editThisLocation/Desktop/sample1.xml', 'r') 
contents_sample1 = sample1File.read() 

sample2File = open('C:/Users/editThisLocation/Desktop/sample2.xml', 'r') 
contents_sample2 = sample2File.read() 


frame1 = Frame(width=768, height=25, bg="#000000", colormap="new") 
frame1.pack() 
Label(frame1, text="sample 1 below - scroll to see more").pack() 

textbox = CustomText(root) 
textbox.insert(END,contents_sample1) 
textbox.pack(expand=1, fill=BOTH) 

frame2 = Frame(width=768, height=25, bg="#000000", colormap="new") 
frame2.pack() 
Label(frame2, text="sample 2 below - scroll to see more").pack() 


textbox1 = CustomText(root) 
textbox1.insert(END,contents_sample2) 
textbox1.pack(expand=1, fill=BOTH) 

sample1 = etree.parse("C:/Users/editThisLocation/Desktop/sample1.xml", parser=recovering_parser).getroot() 
sample2 = etree.parse("C:/Users/editThisLocation/Desktop/sample2.xml", parser=recovering_parser).getroot() 

ToStringsample1 = etree.tostring(sample1) 
sample1String = etree.fromstring(ToStringsample1, parser=recovering_parser) 

ToStringsample2 = etree.tostring(sample2) 
sample2String = etree.fromstring(ToStringsample2, parser=recovering_parser) 

timesample1 = sample1String.findall('{http://www.example.org/eHorizon}time') 
timesample2 = sample2String.findall('{http://www.example.org/eHorizon}time') 

for i,j in zip(timesample1,timesample2):  

    for k,l in zip(i.findall("{http://www.example.org/eHorizon}feature"), j.findall("{http://www.example.org/eHorizon}feature")): 

     if [k.attrib.get('color'), k.attrib.get('type')] != [l.attrib.get('color'), l.attrib.get('type')]: 

      faultyLine = [k.attrib.get('color'), k.attrib.get('type'), k.text] 


def high(event): 

    textbox.tag_configure("yellow", background="yellow") 
    limit_1 = '<p1:time nTimestamp="{0}">'.format(5)  #limit my search between timestamp 5 and timestamp 6 
    limit_2 = '<p1:time nTimestamp="{0}">'.format((5+1)) # timestamp 6 

    highlightString = '<p1:feature color="{0}" type="{1}">{2}</p1:feature>'.format(faultyLine[0],faultyLine[1],faultyLine[2]) #string to be highlighted 

    textbox.highlight_pattern(limit_1, "yellow", start=textbox.search(limit_1, '1.0', stopindex=END), end=textbox.search(limit_2, '1.0', stopindex=END)) 
    textbox.highlight_pattern(highlightString, "yellow", start=textbox.search(limit_1, '1.0', stopindex=END), end=textbox.search(limit_2, '1.0', stopindex=END)) 


button = 'press here to highlight error line' 
c = ttk.Label(root, text=button) 
c.bind("<Button-1>",high) 
c.pack() 

root.mainloop() 

То, что я хочу

Если вы работаете над кодом, было бы представить выход ниже:

my output

Как вы можете видеть на картинке, я только собираюсь, чтобы выделить код, помеченный с зеленой галочкой. Некоторые из вас могут подумать о том, чтобы ограничить начальный и конечный индексы, чтобы выделить этот шаблон. Однако, если вы видите в моей программе, я уже использую начальные и конечные индексы для ограничения моего вывода только nTimestamp="5", и для этого я использую переменные limit_1 и limit_2.

Итак, в этом типе данных, как правильно выделить один шаблон из многих внутри отдельных nTimestamp?

EDIT: Здесь я специально хочу выделить 3-й пункт в nTimestamp="5", потому что этот элемент не присутствует в sample2.xml, как вы можете увидеть в двух XML-файлов и при запуске программы она также отличает это. Единственная проблема заключается в том, чтобы выделить правильный элемент, который является третьим в моем случае.

Я использую класс подсветки из кода Bryan Oakley в here

EDIT Последние

В контексте к тому, что kobejohn спросил ниже в комментариях, целевой файл никогда не будет пустым. Всегда есть вероятность, что целевой файл может иметь дополнительные или отсутствующие элементы. Наконец, мое намерение состоит в том, чтобы выделить только глубокие элементы, которые отличаются или отсутствуют, и timestamps, в котором они расположены. Однако выделение timestamps выполняется правильно, но проблема выделения глубоких элементов, как описано выше, по-прежнему остается проблемой. Спасибо kobejohn для уточнения это.

Примечание:

Один метод, который я знаю, и вы могли бы предположить, что работает правильно, чтобы извлечь индекс зеленого цвета галочкой шаблон и просто запустить выделите тег над ним, но этот подход очень жестко и в больших данных, где вам приходится иметь дело с множеством вариаций, он совершенно неэффективен. Я ищу еще один лучший вариант.

+0

Это здорово, что вы включили полностью функционирующий код и желаемый фактический вывод. Однако мне сложно понять вашу логику. Не могли бы вы объяснить шаг за шагом, как вы хотите выделить текст для выделения? – KobeJohn

+0

Да, конечно, и я прошу прощения за поздний ответ. Текст, который я хочу выделить, состоит из цельной строки в виде строки. Напр. в вышеприведенном коде эта строка определяется в переменной 'highlightString'. Теперь возникает задача идентифицировать из всех элементов 3 'location', которые будут выделены экземпляром' highlightString', поскольку во всех трех элементах местоположения есть одни и те же строки. Поэтому в приведенном выше случае я намерен выделить строку, расположенную в третьем элементе 'location' в' nTimestamp = "5" ' – Dhruvify

+0

Я также добавил еще несколько комментариев к моему коду, которые могут сделать ваше понимание более ясным. – Dhruvify

ответ

3

Это решение работает путем упрощения разницы между base.xml и test.xml на основе описания, которое вы указали. Результатом diff является 3-е дерево XML, которое объединяет исходные деревья.Вывод - это diff с цветовым кодированием для строк, которые не совпадают между файлами.

Надеюсь, вы сможете использовать это или адаптировать его к тому, что вам нужно.

enter image description here

Copy-Paste Script

import copy 
from lxml import etree 
import Tkinter as tk 


# assumption: the root element of both trees is the same 
# note: missing subtrees will only have the parent element highlighted 


def element_content_equal(e1, e2): 
    # starting point here: http://stackoverflow.com/a/24349916/377366 
    try: 
     if e1.tag != e1.tag: 
      return False 
     elif e1.text != e2.text: 
      return False 
     elif e1.tail != e2.tail: 
      return False 
     elif e1.attrib != e2.attrib: 
      return False 
    except AttributeError: 
     # e.g. None is passed in for an element 
     return False 
    return True 


def element_is_in_sequence(element, sequence): 
    for e in sequence: 
     if element_content_equal(e, element): 
      return True 
    return False 


def copy_element_without_children(element): 
    e_copy = etree.Element(element.tag, attrib=element.attrib, nsmap=element.nsmap) 
    e_copy.text = element.text 
    e_copy.tail = element.tail 
    return e_copy 


# start at the root of both xml trees 
parser = etree.XMLParser(recover=True, remove_blank_text=True) 
base_root = etree.parse('base.xml', parser=parser).getroot() 
test_root = etree.parse('test.xml', parser=parser).getroot() 
# each element from the original xml trees will be placed into a merge tree 
merge_root = copy_element_without_children(base_root) 


# additionally each merge tree element will be tagged with its source 
DIFF_ATTRIB = 'diff' 
FROM_BASE_ONLY = 'base' 
FROM_TEST_ONLY = 'test' 

# process the pair of trees, one set of parents at a time 
parent_stack = [(base_root, test_root, merge_root)] 
while parent_stack: 
    base_parent, test_parent, merge_parent = parent_stack.pop() 
    base_children = base_parent.getchildren() 
    test_children = test_parent.getchildren() 

    # compare children and transfer to merge tree 
    base_children_iter = iter(base_children) 
    test_children_iter = iter(test_children) 
    base_child = next(base_children_iter, None) 
    test_child = next(test_children_iter, None) 
    while (base_child is not None) or (test_child is not None): 
     # first handle the case of a unique base child 
     if (base_child is not None) and (not element_is_in_sequence(base_child, test_children)): 
      # base_child is unique: deep copy with base only tag 
      merge_child = copy.deepcopy(base_child) 
      merge_child.attrib[DIFF_ATTRIB] = FROM_BASE_ONLY 
      merge_parent.append(merge_child) 
      # this unique child has already been fully copied to the merge tree so it doesn't go on the stack 
      # only move the base child since test child hasn't been handled yet 
      base_child = next(base_children_iter, None) 
     elif (test_child is not None) and (not element_is_in_sequence(test_child, base_children)): 
      # test_child is unique: deep copy with base only tag 
      merge_child = copy.deepcopy(test_child) 
      merge_child.attrib[DIFF_ATTRIB] = FROM_TEST_ONLY 
      merge_parent.append(merge_child) 
      # this unique child has already been fully copied to the merge tree so it doesn't go on the stack 
      # only move test child since base child hasn't been handled yet 
      test_child = next(test_children_iter, None) 
     elif element_content_equal(base_child, test_child): 
      # both trees share the same element: shallow copy either child with shared tag 
      merge_child = copy_element_without_children(base_child) 
      merge_parent.append(merge_child) 
      # put pair of children on stack as parents to be tested since their children may differ 
      parent_stack.append((base_child, test_child, merge_child)) 
      # move on to next children in both trees since this was a shared element 
      base_child = next(base_children_iter, None) 
      test_child = next(test_children_iter, None) 
     else: 
      raise RuntimeError # there is something wrong - element should be unique or shared. 

# display merge_tree with highlighting to indicate source of each line 
# no highlight: common element in both trees 
# green: line that exists only in test tree (i.e. additional) 
# red: line that exists only in the base tree (i.e. missing) 
root = tk.Tk() 
textbox = tk.Text(root) 
textbox.pack(expand=1, fill=tk.BOTH) 
textbox.tag_config(FROM_BASE_ONLY, background='#ff5555') 
textbox.tag_config(FROM_TEST_ONLY, background='#55ff55') 

# find diff lines to highlight within merge_tree string that includes kludge attributes 
merge_tree_string = etree.tostring(merge_root, pretty_print=True) 
diffs_by_line = [] 
for line, line_text in enumerate(merge_tree_string.split('\n')): 
    for diff_type in (FROM_BASE_ONLY, FROM_TEST_ONLY): 
     if diff_type in line_text: 
      diffs_by_line.append((line+1, diff_type)) 

# remove kludge attributes 
for element in merge_root.iter(): 
    try: 
     del(element.attrib[DIFF_ATTRIB]) 
    except KeyError: 
     pass 
merge_tree_string = etree.tostring(merge_root, pretty_print=True) 

# highlight final lines 
textbox.insert(tk.END, merge_tree_string) 
for line, diff_type in diffs_by_line: 
    textbox.tag_add(diff_type, '{}.0'.format(line), '{}.0'.format(int(line)+1)) 
root.mainloop() 

Входы:

Пожалуйста, обратите внимание, что я очистил XML, потому что я получаю противоречивую поведение с оригинальным XML. Оригинал был в основном с использованием косых черт вместо косых черт, а также с ложными закрывающимися слэшами при открытии тегов.


base.xml (в том же месте, как этот скрипт)

<?xml version="1.0" encoding="UTF-8" standalone="no" ?> 
<p1:sample1 xmlns:p1="http://www.example.org/eHorizon"> 
    <p1:time nTimestamp="5"> 
     <p1:location hours = "1" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="2" type="a">564</p1:feature> 
     <p1:feature color="3" type="b">570</p1:feature> 
     <p1:feature color="4" type="c">570</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="7" type="b">570</p1:feature> 
     <p1:feature color="8" type="c">580</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     </p1:location> 
    </p1:time> 
    <p1:time nTimestamp="6"> 
     <p1:location hours = "1" path = '1'> 
     <p1:feature color="2" type="a">564</p1:feature> 
     <p1:feature color="3" type="b">570</p1:feature> 
     <p1:feature color="4" type="c">570</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="9" type="b">590</p1:feature> 
     <p1:feature color="10" type="c">600</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="7" type="b">570</p1:feature> 
     <p1:feature color="8" type="c">580</p1:feature> 
     </p1:location> 
    </p1:time> 
</p1:sample1> 

test.xml (в том же месте, как этот скрипт)

<?xml version="1.0" encoding="UTF-8" standalone="no" ?> 
<p1:sample1 xmlns:p1="http://www.example.org/eHorizon"> 
    <p1:time nTimestamp="5"> 
     <p1:location hours = "1" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="2" type="a">564</p1:feature> 
     <p1:feature color="3" type="b">570</p1:feature> 
     <p1:feature color="4" type="c">570</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="7" type="b">570</p1:feature> 
     <p1:feature color="8" type="c">580</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="9" type="b">1111</p1:feature> 
     <p1:feature color="10" type="c">2222</p1:feature> 
     </p1:location> 
    </p1:time> 
    <p1:time nTimestamp="6"> 
     <p1:location hours = "1" path = '1'> 
     <p1:feature color="2" type="a">564</p1:feature> 
     <p1:feature color="3" type="b">570</p1:feature> 
     <p1:feature color="4" type="c">570</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="9" type="b">590</p1:feature> 
     <p1:feature color="10" type="c">600</p1:feature> 
     </p1:location> 
     <p1:location hours = "5" path = '1'> 
     <p1:feature color="6" type="a">560</p1:feature> 
     <p1:feature color="7" type="b">570</p1:feature> 
     <p1:feature color="8" type="c">580</p1:feature> 
     </p1:location> 
    </p1:time> 
</p1:sample1> 
+0

Whoa! Я не знаю, как поблагодарить вас. Это блестящее усилие. Прямо сейчас я немного занят подготовкой презентации, поэтому мне все равно нужно включить вашу логику в свою программу. Но я уверен, что он будет работать с минимальными настройками. Помимо решения моего ответа, ваша программа имеет некоторые подходы, о которых я никогда не думал. Я скоро закончу свою работу и вернусь к редактированию. Большое вам спасибо за это. – Dhruvify

+0

@DhruvJ Я рад, что это сработает для вас. Мне тоже понравилось работать с lxml. – KobeJohn

+0

Yea Это очень полезно, и мы можем делать так много всего, используя XML-данные. Меня это даже поражает. Кроме того, я нашел его довольно удобным для пользователей, когда я впервые начал использовать его. – Dhruvify

Смежные вопросы