from . import tool import datetime import html import re def nowiki_js(data): data = data.replace('\\', '\\\\') data = data.replace('"', '\\"') data = data.replace('\r', '') data = re.sub(r'^\n', '', data) data = data.replace('\n', '
') return data def link_fix(main_link, no_change = 0): global end_data main_link = main_link.replace(''', "") if re.search(r'^:', main_link): main_link = re.sub(r'^:', '', main_link) if no_change == 0: main_link = re.sub(r'^사용자:', 'user:', main_link) main_link = re.sub(r'^파일:', 'file:', main_link) main_link = re.sub(r'^분류:', 'category:', main_link) other_link = re.search(r'[^\\]?(#[^#]+)$', main_link) if other_link: other_link = other_link.group(1) main_link = re.sub(r'(#[^#]+)$', '', main_link) else: other_link = '' main_link = main_link.replace("", "'") main_link = main_link.replace('\\#', '%23') find_data = re.findall(r'', main_link) for i in find_data: main_link = main_link.replace('', end_data[i]) find_data = re.findall(r'', other_link) for i in find_data: other_link = other_link.replace('', end_data[i]) return [main_link, other_link] def table_parser(data, cel_data, cel_num, start_data, num = 0, cel_color = {}): table_class = 'class="' div_style = 'style="' all_table = 'style="' cel_style = 'style="' row_style = 'style="' row = '' cel = 'colspan="' + str(round(len(start_data) / 2)) + '"' if not cel_num in cel_color: cel_color[cel_num] = '' cel_style += cel_color[cel_num] if num == 0: if re.search(r'^ ', cel_data) and re.search(r' $', cel_data): cel_style += 'text-align: center;' elif re.search(r'^ ', cel_data): cel_style += 'text-align: right;' elif re.search(r' $', cel_data): cel_style += 'text-align: left;' table_state = re.findall(r'<((?:(?!>).)+)>', data) for in_state in table_state: table_state_data = re.search(r'^([^=]+)=([^=]+)$', in_state) if not table_state_data: if re.search(r'^(#(?:[0-9a-f-A-F]{3}){1,2}|\w+)$', in_state): table_state_data = ['bgcolor', in_state] else: table_state_data = re.search(r'([^0-9]+)([0-9]+)$', in_state) if table_state_data: table_state_data = table_state_data.groups() else: table_state_data = [in_state, ''] else: table_state_data = table_state_data.groups() table_state_data = [ table_state_data[0].replace(' ', ''), re.sub(r',([^,]+)$', '', table_state_data[1]) ] if table_state_data[0] == 'tablewidth': table_data = table_state_data[1] div_style += 'width: ' + ((table_data + 'px') if re.search(r'^[0-9]+$', table_data) else table_data) + ';' all_table += 'width: 100%;' elif table_state_data[0] == 'tableheight': table_data = table_state_data[1] div_style += 'height: ' + ((table_data + 'px') if re.search(r'^[0-9]+$', table_data) else table_data) + ';' elif table_state_data[0] == 'tablealign': table_data = table_state_data[1] if table_data == 'right': div_style += 'float: right;' elif table_data == 'center': div_style += 'margin: auto;' all_table += 'margin: auto;' elif table_state_data[0] == 'tabletextalign': num = 1 table_data = table_state_data[1] if table_data == 'right': all_table += 'text-align: right;' elif table_data == 'center': all_table += 'text-align: center;' elif table_state_data[0] == 'rowtextalign': table_data = table_state_data[1] if table_data == 'right': row_style += 'text-align: right;' elif table_data == 'center': row_style += 'text-align: center;' else: row_style += 'text-align: left;' elif table_state_data[0] == '-': cel = 'colspan="' + table_state_data[1] + '"' elif table_state_data[0] == '^|' or table_state_data[0] == 'v|' or table_state_data[0] == '|': if table_state_data[0][0] == '^': cel_style += 'vertical-align: top;' elif table_state_data[0][0] == 'v': cel_style += 'vertical-align: bottom;' row = 'rowspan="' + table_state_data[1] + '"' elif table_state_data[0] == 'rowbgcolor': table_data = table_state_data[1] row_style += 'background: ' + table_data + ';' elif table_state_data[0] == 'rowcolor': table_data = table_state_data[1] row_style += 'color: ' + table_data + ';' elif table_state_data[0] == 'tablebordercolor': table_data = table_state_data[1] all_table += 'border: ' + table_data + ' 2px solid;' elif table_state_data[0] == 'tablebgcolor': table_data = table_state_data[1] all_table += 'background: ' + table_data + ';' elif table_state_data[0] == 'tablecolor': table_data = table_state_data[1] all_table += 'color: ' + table_data + ';' elif table_state_data[0] == 'colbgcolor': table_data = table_state_data[1] table_data = table_data cel_color[cel_num] += 'background: ' + table_data + ';' cel_style += 'background: ' + table_data + ';' elif table_state_data[0] == 'colcolor': table_data = table_state_data[1] table_data = table_data cel_color[cel_num] += 'color: ' + table_data + ';' cel_style += 'color: ' + table_data + ';' elif table_state_data[0] == 'bgcolor': table_data = table_state_data[1] cel_style += 'background: ' + table_data + ';' elif table_state_data[0] == 'color': table_data = table_state_data[1] cel_style += 'color: ' + table_data + ';' elif table_state_data[0] == 'width': table_data = table_state_data[1] cel_style += 'width: ' + ((table_data + 'px') if re.search(r'^[0-9]+$', table_data) else table_data) + ';' elif table_state_data[0] == 'height': table_data = table_state_data[1] cel_style += 'height: ' + ((table_data + 'px') if re.search(r'^[0-9]+$', table_data) else table_data) + ';' elif table_state_data[0] == '(' or table_state_data[0] == ':' or table_state_data[0] == ')': if table_state_data[0] == '(': cel_style += 'text-align: right;' elif table_state_data[0] == ':': cel_style += 'text-align: center;' else: cel_style += 'text-align: left;' elif table_state_data[0] == 'tableclass': table_data = table_state_data[1] table_class += table_data div_style += '"' all_table += '"' cel_style += '"' row_style += '"' table_class += '"' return [all_table, row_style, cel_style, row, cel, table_class, num, div_style, cel_color] def table_start(data): data = re.sub(r'\n( +)\|\|', '\n||', data) data = re.sub(r'\|\|( +)\n', '||\n', data) data = re.sub(r'(\|\|)+\n', '||\n', data) while 1: cel_num = 0 table_num = 0 table_end = '' cel_color = {} table = re.search(r'\n((?:(?:(?:(?:\|\||\|[^|]+\|)+(?:(?:(?!\|\|).\n*)*))+)\|\|(?:\n)?)+)', data) if table: table = re.sub(r'(\|\|)+\n', '||\n', table.group(1)) table_caption = re.search(r'^\|([^|]+)\|', table) if table_caption: table_caption = '' + table_caption.group(1) + '' table = re.sub(r'^\|([^|]+)\|', '||', table) else: table_caption = '' table = '\n' + table table_cel = re.findall(r'(\n(?:(?:\|\|)+)|\|\|\n(?:(?:\|\|)+)|(?:(?:\|\|)+))((?:(?:(?!\n|\|\|).)+\n*)+)', table) for i in table_cel: cel_plus = re.search(r'^((?:<(?:(?:(?!>).)*)>)+)', i[1]) cel_plus = cel_plus.group(1) if cel_plus else '' cel_data = re.sub(r'^((?:<(?:(?:(?!>).)*)>)+)', '', i[1]) if re.search(r'^\n', i[0]): cel_num = 1 cel_plus = table_parser( cel_plus, cel_data, cel_num, re.sub(r'^\n', '', i[0]), table_num, cel_color ) cel_color = cel_plus[8] table_num = cel_plus[6] table_end += '' + \ '
' + \ '' + \ table_caption + \ '' + \ '' + \ '' + \ '' + \ '' + \ '' + \ '' + \ '
' + \ cel_data elif re.search(r'\n', i[0]): cel_num = 1 cel_plus = table_parser( cel_plus, cel_data, cel_num, re.sub(r'^\|\|\n', '', i[0]), table_num, cel_color ) cel_color = cel_plus[8] table_end += '' + \ '
' + \ cel_data else: cel_num += 1 cel_plus = table_parser( cel_plus, cel_data, cel_num, re.sub(r'^\|\|\n', '', i[0]), table_num, cel_color ) cel_color = cel_plus[8] table_end += '' + \ '' + \ cel_data table_end += '' + \ '
' + \ '
' + \ '' data = re.sub(r'\n((?:(?:(?:(?:\|\||\|[^|]+\|)+(?:(?:(?!\|\|).\n*)*))+)\|\|(?:\n)?)+)', '\n' + table_end + '\n', data, 1) else: break return data.replace('||', '') def middle_parser(data): global end_data global plus_data global nowiki_num global include_name middle_stack = 0 middle_list = [] middle_num = 0 html_num = 0 syntax_num = 0 folding_num = 0 middle_re = re.compile(r'(?:{{{((?:(?:(?! |{{{|}}}|<).)*) ?)|(}}}))') middle_all_data = middle_re.findall(data) for middle_data in middle_all_data: if not middle_data[1]: if middle_stack > 0: middle_stack += 1 data = re.sub(r'(?:{{{((?:(?! |{{{|}}}|<).)*)(?P ?)|(}}}))', '' + middle_data[0] + '\g', data, 1) else: if re.search(r'^(#|@|\+|\-)', middle_data[0]) and not re.search(r'^(#|@|\+|\-){2}|(#|@|\+|\-)\\', middle_data[0]): if re.search(r'^(#(?:[0-9a-f-A-F]{3}){1,2})', middle_data[0]): middle_search = re.search(r'^(#(?:[0-9a-f-A-F]{3}){1,2})', middle_data[0]) middle_list += ['span'] data = middle_re.sub('', data, 1) elif re.search(r'^(?:#(\w+))', middle_data[0]): middle_search = re.search(r'^(?:#(\w+))', middle_data[0]) middle_list += ['span'] data = middle_re.sub('', data, 1) elif re.search(r'^(?:@((?:[0-9a-f-A-F]{3}){1,2}))', middle_data[0]): middle_search = re.search(r'^(?:@((?:[0-9a-f-A-F]{3}){1,2}))', middle_data[0]) middle_list += ['span'] data = middle_re.sub('', data, 1) elif re.search(r'^(?:@(\w+))', middle_data[0]): middle_search = re.search(r'^(?:@(\w+))', middle_data[0]) middle_list += ['span'] data = middle_re.sub('', data, 1) elif re.search(r'^(\+|-)([1-5])', middle_data[0]): middle_search = re.search(r'^(\+|-)([1-5])', middle_data[0]) middle_search = middle_search.groups() if middle_search[0] == '+': font_size = str(int(middle_search[1]) * 20 + 100) else: font_size = str(100 - int(middle_search[1]) * 10) middle_list += ['span'] data = middle_re.sub('', data, 1) elif re.search(r'^#!wiki', middle_data[0]): middle_data_2 = re.search(r'{{{#!wiki(?: style=(?:"|')((?:(?!"|').)*)(?:"|'))?(?: *)\n?', data) if middle_data_2: middle_data_2 = middle_data_2.groups() else: middle_data_2 = [''] middle_list += ['div_1'] data = re.sub( r'{{{#!wiki(?: style=(?:"|')((?:(?!"|').)*)(?:"|'))?(?: *)\n?', '', data, 1 ) elif re.search(r'^#!syntax', middle_data[0]): middle_data_2 = re.search(r'{{{#!syntax ((?:(?!\n|{{{).)+)\n?', data) if middle_data_2: middle_data_2 = middle_data_2.groups() else: middle_data_2 = ['python'] if syntax_num == 0: plus_data += 'hljs.initHighlightingOnLoad();\n' syntax_num = 1 middle_list += ['pre'] data = re.sub( r'{{{#!syntax ?((?:(?!\n|{{{).)*)\n?', '
',
                            data,
                            1
                        )
                    elif re.search(r'^#!folding', middle_data[0]):
                        middle_list += ['div_dd']

                        folding_data = re.search(r'{{{#!folding ?((?:(?!\n).)*)\n?', data)
                        if folding_data:
                            folding_data = folding_data.groups()
                        else:
                            folding_data = ['Test']

                        plus_data += '' + \
                            'if(document.getElementById("get_' + include_name + 'folding_' + str(folding_num) + '")) { ' + \
                                'document.getElementById("get_' + include_name + 'folding_' + str(folding_num) + '").innerHTML = ' + \
                                    '"' + nowiki_js(folding_data[0]) + '"; ' + \
                                '' + \
                            '}' + \
                            '\n' + \
                        ''
                        data = re.sub(
                            r'{{{#!folding ?((?:(?!\n).)*)\n?', '' + \
                            '
' + \ '
' + \ '' + \ '' + \ '' + \ '' + \ '' + \ '
', data, 1) else: data = middle_re.sub('', data, 1) del middle_list[middle_num] while 1: if middle_list == []: break else: if middle_stack > 0: middle_stack -= 1 if middle_stack > 0: data += '' else: if middle_num > 0: middle_num -= 1 if middle_list[middle_num] == '2div': data += '
' elif middle_list[middle_num] == 'pre': data += '' else: data += '' del middle_list[middle_num] data = data.replace('', '{{{') data = data.replace('', '}}}') while 1: nowiki_data = re.search(r'((?:(?:(?!<\/code>).)*\n*)*)<\/code>', data) if nowiki_data: nowiki_data = nowiki_data.groups() nowiki_num += 1 end_data[include_name + 'nowiki_' + str(nowiki_num)] = nowiki_data[0] plus_data += '' + \ 'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \ 'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(nowiki_data[0]) + '";\n' + \ '}\n' + \ '' data = re.sub( r'((?:(?:(?!<\/code>).)*\n*)*)<\/code>', '', data, 1 ) else: break while 1: syntax_data = re.search( r']+)">((?:(?:(?:(?!<\/code>|', data ) if syntax_data: syntax_data = syntax_data.groups() nowiki_num += 1 end_data[include_name + 'nowiki_' + str(nowiki_num)] = syntax_data[1] plus_data += '' + \ 'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \ 'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(syntax_data[1]) + '";\n' + \ '}\n' + \ '' data = re.sub( r']+)">((?:(?:(?:(?!<\/code>|', '', data, 1 ) else: break return data def namumark(conn, data, title, include_num): curs = conn.cursor() global plus_data global end_data global nowiki_num global include_name nowiki_num = 0 data = '\n' + data + '\n' include_name = include_num + '_' if include_num else '' plus_data = '' backlink = [] end_data = {} data = re.sub(r'@([^=@]+)=(?P[^=@]+)@', '\g', data) data = re.sub(r'(?P(?:(?!<\/math>).)+)<\/math>', '[math(\g)]', data) data = html.escape(data) data = data.replace('\r\n', '\n') math_re = re.compile(r'\[math\(((?:(?!\)\]).)+)\)\]', re.I) while 1: math = math_re.search(data) if math: math = math.group(1) math = math.replace('{', '') math = math.replace('}', '') math = math.replace('\\', '') data = math_re.sub('' + math + '', data, 1) else: break data = data.replace('\\{', '') data = middle_parser(data) data = data.replace('', '\\{') first = 0 math_re = re.compile(r'((?:(?!<\/math>).)+)<\/math>', re.I) while 1: math = math_re.search(data) if math: math = math.group(1) math = math.replace('', '{') math = math.replace('', '}') math = math.replace('', '\\') first += 1 data = math_re.sub('', data, 1) plus_data += '' + \ 'try {\n' + \ 'katex.render("' + nowiki_js(html.unescape(math)) + '", document.getElementById(\"' + include_name + 'math_' + str(first) + '\"));\n' + \ '} catch {\n' + \ 'document.getElementById(\"' + include_name + 'math_' + str(first) + '\").innerHTML = "' + nowiki_js(math) + '";\n' + \ '}\n' + \ '' else: break num = 0 while 1: one_nowiki = re.search(r'(?:\\)(.)', data) if one_nowiki: one_nowiki = one_nowiki.groups() nowiki_num += 1 end_data[include_name + 'nowiki_' + str(nowiki_num)] = one_nowiki[0] plus_data += '' + \ 'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \ 'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(one_nowiki[0]) + '";\n' + \ '}\n' + \ '' data = re.sub(r'(?:\\)(.)', '', data, 1) else: break include_re = re.compile(r'\[include\(((?:(?!\)\]).)+)\)\]', re.I) i = 0 while 1: i += 1 include = include_re.search(data) if include: include = include.group(1) include_data = re.search(r'^((?:(?!,).)+)', include) if include_data: include_data = include_data.group(1) else: include_data = 'Test' include_link = include_data backlink += [[title, include_link, 'include']] data = include_re.sub('' + \ '(' + include_link + ')' + \ '
' + \ '', data, 1) include_plus_data = [] while 1: include_plus = re.search(r', ?((?:(?!=).)+)=((?:(?!,).)+)', include) if include_plus: include_plus = include_plus.groups() include_data_set = include_plus[1] find_data = re.findall(r'', include_data_set) for j in find_data: include_data_set = include_data_set.replace('', end_data[j]) include_plus_data += [[include_plus[0], include_data_set]] include = re.sub(r', ?((?:(?!=).)+)=((?:(?!,).)+)', '', include, 1) else: break plus_data += 'load_include("' + include_link + '", "' + include_name + 'include_' + str(i) + '", ' + str(include_plus_data) + ');\n' else: break data = data.replace('&', '&') data = re.sub(r'\n##[^\n]+', '', data) div_1_re = re.compile(r']+)>((?:(?!).|\n*)+)<\/div_1>') while 1: wiki_table_data = div_1_re.search(data) if wiki_table_data: wiki_table_data = wiki_table_data.groups() if re.search(r'\|\|', wiki_table_data[1]): end_parser = table_start('\n' + wiki_table_data[1] + '\n') end_parser = re.sub(r'^\n', '', end_parser) end_parser = re.sub(r'\n$', '', end_parser) else: end_parser = wiki_table_data[1] data = div_1_re.sub('
' + end_parser + '', data, 1) else: break data = data.replace('', '
') data = data.replace('', '') data += '\n' data = data.replace('\\', '\') redirect_re = re.compile(r'\n#(?:redirect|넘겨주기) ([^\n]+)', re.I) redirect = redirect_re.search(data) if redirect: redirect = redirect.group(1) return_link = link_fix(redirect) main_link = html.unescape(return_link[0]) other_link = return_link[1] backlink += [[title, main_link, 'redirect']] plus_data += '' + \ 'var get_link = window.location.search.match(/(?:\?|&)from=([^&]+)/);\n' + \ 'if(!get_link) {\n' + \ 'window.location.href = "/w/' + tool.url_pas(main_link) + other_link + '?from=' + tool.url_pas(title) + '";\n' + \ '}\n' + \ '' data = redirect_re.sub('', data, 1) no_toc_re = re.compile(r'\[(?:목차|toc)\((?:no)\)\]\n', re.I) toc_re = re.compile(r'\[(?:목차|toc)\]', re.I) if not no_toc_re.search(data): if not toc_re.search(data): data = re.sub(r'(?P\n(={1,6})(#)? ?((?:(?!(?: #=| =)).)+) ?#?(?:=+)\n)', '\n[toc]\g', data, 1) else: data = no_toc_re.sub('', data) data = '
' + data toc_stack = [0, 0, 0, 0, 0, 0] toc_num = 0 toc_data = '' + \ '
' + \ 'TOC' + \ '
' + \ '
' + \ '' edit_num = 0 toc_head_re = re.compile(r'\n(={1,6})(#)? ?((?:(?!(?: #=| =)).)+) ?#?(?:=+)\n') while 1: toc = toc_head_re.search(data) if toc: toc = toc.groups() edit_num += 1 toc_len_num = len(toc[0]) toc_len_str = str(toc_len_num) toc_len_num -= 1 toc_stack[toc_len_num] += 1 for i in range(toc_len_num + 1, 6): toc_stack[i] = 0 edit_num_str = str(edit_num) toc_level_str = '.'.join([str(i) for i in toc_stack if i != 0]) toc_fol = '+' if toc[1] else '-' data = toc_head_re.sub( '\n' + \ '
' '' + \ '' + toc_level_str + '. ' + toc[2] + ' ' + \ '' + \ '(Edit)' + \ ' ' + \ '' + \ '(' + toc_fol + ')' + \ '' + \ '' + \ '' + \ '' data = toc_re.sub(toc_data, data) macro_re = re.compile(r'\[([^[(]+)\(((?:(?!\[|\)]).)+)\)\]') macro_data = macro_re.findall(data) for i in macro_data: macro_name = i[0].lower() if macro_name == 'youtube' or macro_name == 'kakaotv' or macro_name == 'nicovideo': width = re.search(r', ?width=((?:(?!,).)+)', i[1]) if width: video_width = width.group(1) if re.search(r'^[0-9]+$', video_width): video_width += 'px' else: video_width = '560px' height = re.search(r', ?height=((?:(?!,).)+)', i[1]) if height: video_height = height.group(1) if re.search(r'^[0-9]+$', video_height): video_height += 'px' else: video_height = '315px' code = re.search(r'^((?:(?!,).)+)', i[1]) if code: video_code = code.group(1) else: video_code = '' video_start = '' if macro_name == 'youtube': start = re.search(r', ?(start=(?:(?!,).)+)', i[1]) if start: video_start = '?' + start.group(1) video_code = re.sub(r'^https:\/\/www\.youtube\.com\/watch\?v=', '', video_code) video_code = re.sub(r'^https:\/\/youtu\.be\/', '', video_code) video_src = 'https://www.youtube.com/embed/' + video_code elif macro_name == 'kakaotv': video_code = re.sub(r'^https:\/\/tv\.kakao\.com\/channel\/9262\/cliplink\/', '', video_code) video_code = re.sub(r'^http:\/\/tv\.kakao\.com\/v\/', '', video_code) video_src = 'https://tv.kakao.com/embed/player/cliplink/' + video_code +'?service=kakao_tv' else: video_src = 'https://embed.nicovideo.jp/watch/' + video_code data = macro_re.sub( '', data, 1 ) elif macro_name == 'anchor': data = macro_re.sub('', data, 1) elif macro_name == 'ruby': ruby_code = re.search(r'^([^,]+)', i[1]) if ruby_code: ruby_code = ruby_code.group(1) else: ruby_code = 'Test' ruby_top = re.search(r'ruby=([^,]+)', i[1], flags = re.I) if ruby_top: ruby_top = ruby_top.group(1) else: ruby_top = 'Test' ruby_color = re.search(r'color=([^,]+)', i[1], flags = re.I) if ruby_color: ruby_color = 'color: ' + ruby_color.group(1) + ';' else: ruby_color = '' ruby_data = '' + \ '' + \ ruby_code \ + '(' + \ '' + ruby_top + '' + \ ')' + \ '' + \ '' data = macro_re.sub(ruby_data, data, 1) elif macro_name == 'age' or macro_name == 'dday': try: old = datetime.datetime.strptime(time, '%Y-%m-%d') will = datetime.datetime.strptime(i[1], '%Y-%m-%d') e_data = old - will if macro_name == 'age': data = macro_re.sub(str(int(e_data.days / 365)), data, 1) else: data = macro_re.sub((str(e_data.days) if re.search(r'^-', str(e_data.days)) else ('+' + str(e_data.days))), data, 1) except: data = macro_re.sub('age-dday-error', data, 1) else: data = macro_re.sub('' + i[0] + '' + i[1] + '', data, 1) data = data.replace('', '[') data = data.replace('', '(') data = data.replace('', ')]') blockquote_re = re.compile(r'(\n(?:> ?(?:[^\n]*)\n)+)') while 1: blockquote = blockquote_re.search(data) if blockquote: blockquote = re.sub(r'^\n> ?', '', blockquote.group(1)) blockquote = re.sub(r'\n> ?', '\n', blockquote) blockquote = re.sub(r'\n$', '', blockquote) data = blockquote_re.sub('\n
' + blockquote + '
\n', data, 1) else: break hr_re = re.compile(r'\n-{4,9}\n') while 1: if hr_re.search(data): data = hr_re.sub('\n
\n', data, 1) else: break data = re.sub(r'(?P\n +\* ?(?:(?:(?!\|\|).)+))\|\|', '\g\n ||', data) data = re.sub(r'(?P