637 lines
22 KiB
Python
637 lines
22 KiB
Python
from collections import defaultdict, Counter
|
|
import enum
|
|
import re
|
|
import networkx as nx
|
|
import penman
|
|
|
|
from hanlp.components.amr.seq2seq.dataset.penman import pm_encode
|
|
|
|
BACKOFF = penman.Graph([
|
|
penman.Triple('d2', ':instance', 'dog'),
|
|
penman.Triple('b1', ':instance', 'bark-01'),
|
|
penman.Triple('b1', ':ARG0', 'd2'), ])
|
|
|
|
|
|
def token_processing(tok):
|
|
if tok is None:
|
|
return None
|
|
elif tok.isdigit():
|
|
try:
|
|
return eval(tok)
|
|
except:
|
|
return tok
|
|
elif tok.startswith('"') and (not tok.endswith('"')):
|
|
return tok + '"'
|
|
elif tok.endswith('"') and (not tok.startswith('"')):
|
|
return '"' + tok
|
|
else:
|
|
return tok
|
|
|
|
|
|
def decode_into_node_and_backreferences(subtoken_ids, tokenizer):
|
|
rex_arg = re.compile(f"^{tokenizer.INIT}(op|snt|conj|prep)")
|
|
rex_spc = re.compile(r"<(s|/s|lit|/lit|stop|unk|pad|mask)>")
|
|
|
|
# get strings
|
|
subtokens = tokenizer.convert_ids_to_tokens(subtoken_ids)
|
|
# fix backreferences
|
|
subtoken_backreferences = [max(t - len(tokenizer), -1) for t in subtoken_ids]
|
|
# strip padding
|
|
no_pad = [(s, b) for s, b in zip(subtokens, subtoken_backreferences) if s != (tokenizer.INIT + '<pad>')]
|
|
if no_pad:
|
|
subtokens, subtoken_backreferences = zip(*no_pad)
|
|
else:
|
|
subtokens, subtoken_backreferences = ['<s>'], [-1]
|
|
|
|
# subword collapse
|
|
tokens = []
|
|
backreferences = []
|
|
subword_to_token_map = {}
|
|
current_token_i = 0
|
|
for subw_i, (subw_backr, subtok) in enumerate(zip(subtoken_backreferences, subtokens)):
|
|
subword_to_token_map[subw_i] = current_token_i
|
|
|
|
# if empty you cannot do anything but add a new word
|
|
if not tokens:
|
|
tokens.append(subtok.lstrip(tokenizer.INIT))
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# backref can't be splitted
|
|
elif subw_backr > -1:
|
|
tokens.append(None)
|
|
backreferences.append(subword_to_token_map[subw_backr])
|
|
current_token_i += 1
|
|
|
|
# after a special token release
|
|
elif isinstance(tokens[-1], str) and rex_spc.match(tokens[-1]):
|
|
tokens.append(subtok.lstrip(tokenizer.INIT))
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# after a subtoken ':' (which should be followed by the rest of the edge) ignore tokenizer.INIT
|
|
# TODO: this is an ugly patch due to the fact that BART tokenizer splits after ':'
|
|
elif (tokens[-1] == ':') and rex_arg.match(subtok):
|
|
tokens[-1] = tokens[-1] + subtok[1:]
|
|
|
|
# leading tokenizer.INIT
|
|
elif subtok.startswith(tokenizer.INIT):
|
|
tokens.append(subtok.lstrip(tokenizer.INIT))
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# very ugly patch for some cases in which tokenizer.INIT is not in the following token to the edge
|
|
elif isinstance(tokens[-1], str) and tokens[-1].startswith(':') and tokens[-1][-1].isdigit() and (
|
|
subtok != '-of'):
|
|
tokens.append(subtok.lstrip(tokenizer.INIT))
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# in any other case attach to the previous
|
|
else:
|
|
tokens[-1] = tokens[-1] + subtok
|
|
|
|
# strip INIT and fix byte-level
|
|
tokens = [tokenizer.convert_tokens_to_string(list(t)).lstrip() if isinstance(t, str) else t for t in tokens]
|
|
# tokens = [t.replace(tokenizer.INIT, '') if isinstance(t, str) else t for t in tokens]
|
|
|
|
# unks are substituted with thing
|
|
tokens = [t if t != '<unk>' else 'thing' for t in tokens]
|
|
|
|
old_tokens = tokens
|
|
old_backreferences = backreferences
|
|
|
|
# <lit> Barack Obama </lit> -> "Barack Obama"
|
|
tokens = []
|
|
backreferences = []
|
|
token_to_token_map = {}
|
|
start_search = 0
|
|
removed = 0
|
|
while True:
|
|
try:
|
|
|
|
lit_start = old_tokens.index('<lit>', start_search)
|
|
token_addition = old_tokens[start_search:lit_start]
|
|
for i, t in enumerate(token_addition, start=start_search):
|
|
token_to_token_map[i] = i - removed
|
|
tokens += token_addition
|
|
|
|
backreferences_addition = [token_to_token_map[b] if b > -1 else -1 for b in
|
|
old_backreferences[start_search:lit_start]]
|
|
backreferences += backreferences_addition
|
|
|
|
lit_end = min(lit_start + 2, len(old_tokens) - 1)
|
|
|
|
while lit_end < len(old_tokens):
|
|
old_tok = old_tokens[lit_end]
|
|
|
|
if isinstance(old_tok, str) and (
|
|
(old_tok.startswith(':') and len(old_tok) > 3) or (old_tok == '<stop>')):
|
|
res_tok = old_tokens[lit_start + 1:lit_end]
|
|
for i in range(lit_start, lit_end):
|
|
token_to_token_map[i] = len(tokens)
|
|
|
|
# Remove possible wrong None
|
|
res = old_tokens[lit_start + 1:lit_end]
|
|
res = [str(r) for r in res if r is not None]
|
|
res = '"' + '_'.join(res) + '"'
|
|
|
|
removed += len(res_tok)
|
|
start_search = lit_end
|
|
tokens += [res, old_tok]
|
|
backreferences += [-1, -1]
|
|
break
|
|
|
|
elif old_tok == '</lit>':
|
|
res_tok = old_tokens[lit_start + 1:lit_end]
|
|
for i in range(lit_start, lit_end + 1):
|
|
token_to_token_map[i] = len(tokens)
|
|
|
|
# Remove possible wrong None
|
|
res = old_tokens[lit_start + 1:lit_end]
|
|
res = [str(r) for r in res if r is not None]
|
|
res = '"' + '_'.join(res) + '"'
|
|
|
|
removed += len(res_tok) + 1
|
|
start_search = lit_end + 1
|
|
tokens.append(res)
|
|
backreferences.append(-1)
|
|
break
|
|
|
|
else:
|
|
lit_end += 1
|
|
start_search = lit_end
|
|
|
|
except ValueError:
|
|
token_addition = old_tokens[start_search:]
|
|
for i, t in enumerate(token_addition, start=start_search):
|
|
token_to_token_map[i] = i - removed
|
|
backreferences_addition = [token_to_token_map[b] if b > -1 else b for b in
|
|
old_backreferences[start_search:]]
|
|
tokens += token_addition
|
|
backreferences += backreferences_addition
|
|
break
|
|
|
|
tokens = [token_processing(t) for t in tokens]
|
|
|
|
shift = 1
|
|
if len(tokens) > 1 and tokens[1] == '<s>':
|
|
shift = 2
|
|
|
|
tokens = tokens[shift:]
|
|
backreferences = [b if b == -1 else b - shift for b in backreferences[shift:]]
|
|
|
|
if tokens and tokens[-1] == '</s>':
|
|
tokens.pop()
|
|
backreferences.pop()
|
|
|
|
return tokens, backreferences
|
|
|
|
|
|
def decode_into_node_and_backreferences_without_space(subtoken_ids, tokenizer):
|
|
rex_arg = re.compile(f"^{tokenizer.INIT}(op|snt|conj|prep)")
|
|
rex_spc = re.compile(r"<(s|/s|lit|/lit|stop|unk|pad|mask)>")
|
|
|
|
# get strings
|
|
subtokens = tokenizer.convert_ids_to_tokens(subtoken_ids)
|
|
# fix backreferences
|
|
subtoken_backreferences = [max(t - len(tokenizer), -1) for t in subtoken_ids]
|
|
# strip padding
|
|
no_pad = [(s, b) for s, b in zip(subtokens, subtoken_backreferences) if s != (tokenizer.INIT + '<pad>')]
|
|
if no_pad:
|
|
subtokens, subtoken_backreferences = zip(*no_pad)
|
|
else:
|
|
subtokens, subtoken_backreferences = ['<s>'], [-1]
|
|
|
|
# subword collapse
|
|
tokens = []
|
|
backreferences = []
|
|
subword_to_token_map = {}
|
|
current_token_i = 0
|
|
prev_is_pointer = False
|
|
prev_is_rel = False
|
|
for subw_i, (subw_backr, subtok) in enumerate(zip(subtoken_backreferences, subtokens)):
|
|
subword_to_token_map[subw_i] = current_token_i
|
|
is_pointer = subtok.startswith('<pointer:') and subtok.endswith('>')
|
|
is_rel = subtok.startswith(':') and len(subtok) > 1
|
|
is_bracket = subtok in '()'
|
|
|
|
# if empty you cannot do anything but add a new word
|
|
if not tokens:
|
|
tokens.append(subtok)
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# backref can't be splitted
|
|
elif subw_backr > -1:
|
|
tokens.append(None)
|
|
backreferences.append(subword_to_token_map[subw_backr])
|
|
current_token_i += 1
|
|
|
|
# after a special token release
|
|
elif isinstance(tokens[-1], str) and rex_spc.match(tokens[-1]):
|
|
tokens.append(subtok)
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# after a subtoken ':' (which should be followed by the rest of the edge) ignore tokenizer.INIT
|
|
# TODO: this is an ugly patch due to the fact that BART tokenizer splits after ':'
|
|
elif (tokens[-1] == ':') and rex_arg.match(subtok):
|
|
tokens[-1] = tokens[-1] + subtok[1:]
|
|
|
|
# current or prev is a control token
|
|
elif (is_pointer or is_rel or prev_is_pointer or prev_is_rel or is_bracket or subtok == '</s>') \
|
|
and subtok != '-of':
|
|
tokens.append(subtok)
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# very ugly patch for some cases in which tokenizer.INIT is not in the following token to the edge
|
|
elif isinstance(tokens[-1], str) and tokens[-1].startswith(':') and tokens[-1][-1].isdigit() and (
|
|
subtok != '-of'):
|
|
tokens.append(subtok)
|
|
backreferences.append(-1)
|
|
current_token_i += 1
|
|
|
|
# in any other case attach to the previous
|
|
else:
|
|
tokens[-1] = tokens[-1] + subtok
|
|
|
|
prev_is_pointer = is_pointer
|
|
prev_is_rel = is_rel
|
|
|
|
# strip INIT and fix byte-level
|
|
tokens = [tokenizer.convert_tokens_to_string(list(t)).lstrip() if isinstance(t, str) else t for t in tokens]
|
|
# tokens = [t.replace(tokenizer.INIT, '') if isinstance(t, str) else t for t in tokens]
|
|
|
|
# unks are substituted with thing
|
|
tokens = [t if t != '<unk>' else 'thing' for t in tokens]
|
|
|
|
old_tokens = tokens
|
|
old_backreferences = backreferences
|
|
|
|
# <lit> Barack Obama </lit> -> "Barack Obama"
|
|
tokens = []
|
|
backreferences = []
|
|
token_to_token_map = {}
|
|
start_search = 0
|
|
removed = 0
|
|
while True:
|
|
try:
|
|
|
|
lit_start = old_tokens.index('<lit>', start_search)
|
|
token_addition = old_tokens[start_search:lit_start]
|
|
for i, t in enumerate(token_addition, start=start_search):
|
|
token_to_token_map[i] = i - removed
|
|
tokens += token_addition
|
|
|
|
backreferences_addition = [token_to_token_map[b] if b > -1 else -1 for b in
|
|
old_backreferences[start_search:lit_start]]
|
|
backreferences += backreferences_addition
|
|
|
|
lit_end = min(lit_start + 2, len(old_tokens) - 1)
|
|
|
|
while lit_end < len(old_tokens):
|
|
old_tok = old_tokens[lit_end]
|
|
|
|
if isinstance(old_tok, str) and (
|
|
(old_tok.startswith(':') and len(old_tok) > 3) or (old_tok == '<stop>')):
|
|
res_tok = old_tokens[lit_start + 1:lit_end]
|
|
for i in range(lit_start, lit_end):
|
|
token_to_token_map[i] = len(tokens)
|
|
|
|
# Remove possible wrong None
|
|
res = old_tokens[lit_start + 1:lit_end]
|
|
res = [str(r) for r in res if r is not None]
|
|
res = '"' + '_'.join(res) + '"'
|
|
|
|
removed += len(res_tok)
|
|
start_search = lit_end
|
|
tokens += [res, old_tok]
|
|
backreferences += [-1, -1]
|
|
break
|
|
|
|
elif old_tok == '</lit>':
|
|
res_tok = old_tokens[lit_start + 1:lit_end]
|
|
for i in range(lit_start, lit_end + 1):
|
|
token_to_token_map[i] = len(tokens)
|
|
|
|
# Remove possible wrong None
|
|
res = old_tokens[lit_start + 1:lit_end]
|
|
res = [str(r) for r in res if r is not None]
|
|
res = '"' + '_'.join(res) + '"'
|
|
|
|
removed += len(res_tok) + 1
|
|
start_search = lit_end + 1
|
|
tokens.append(res)
|
|
backreferences.append(-1)
|
|
break
|
|
|
|
else:
|
|
lit_end += 1
|
|
start_search = lit_end
|
|
|
|
except ValueError:
|
|
token_addition = old_tokens[start_search:]
|
|
for i, t in enumerate(token_addition, start=start_search):
|
|
token_to_token_map[i] = i - removed
|
|
backreferences_addition = [token_to_token_map[b] if b > -1 else b for b in
|
|
old_backreferences[start_search:]]
|
|
tokens += token_addition
|
|
backreferences += backreferences_addition
|
|
break
|
|
|
|
tokens = [token_processing(t) for t in tokens]
|
|
|
|
shift = 0
|
|
if len(tokens) > 1 and tokens[1] == '<s>':
|
|
shift = 1
|
|
|
|
tokens = tokens[shift:]
|
|
backreferences = [b if b == -1 else b - shift for b in backreferences[shift:]]
|
|
|
|
if tokens and tokens[-1] == '</s>':
|
|
tokens.pop()
|
|
backreferences.pop()
|
|
|
|
return tokens, backreferences
|
|
|
|
|
|
def index_of(element, iterable, default=None, start=None, end=None):
|
|
if not callable(element):
|
|
def check(x):
|
|
return element == x
|
|
else:
|
|
check = element
|
|
if start is None:
|
|
start = 0
|
|
if end is None:
|
|
end = len(iterable)
|
|
item = start
|
|
while item < end:
|
|
if check(iterable[item]):
|
|
return item
|
|
item += 1
|
|
return default
|
|
|
|
|
|
def separate_edges_nodes(edges_nodes_slice, *other):
|
|
is_arg = lambda x: isinstance(x, str) and x.startswith(':')
|
|
start = 0
|
|
edges = []
|
|
nodes = []
|
|
l = len(edges_nodes_slice)
|
|
while start < l:
|
|
edge_index = index_of(
|
|
is_arg,
|
|
edges_nodes_slice,
|
|
start=start)
|
|
if edge_index is None or edge_index == (l - 1):
|
|
break
|
|
if is_arg(edges_nodes_slice[edge_index + 1]):
|
|
start = edge_index + 1
|
|
continue
|
|
edges.append(edge_index)
|
|
nodes.append(edge_index + 1)
|
|
start = edge_index + 2
|
|
ret = []
|
|
for oth in other:
|
|
edges_oth = [oth[i] for i in edges]
|
|
nodes_oth = [oth[i] for i in nodes]
|
|
ret.append((edges_oth, nodes_oth))
|
|
return ret
|
|
|
|
|
|
def _split_name_ops(graph):
|
|
# identify name triples
|
|
name_vars = {}
|
|
for i, (v1, rel, v2) in enumerate(graph.triples):
|
|
if rel == ':instance' and v2 == 'name':
|
|
name_vars[v1] = 1
|
|
|
|
# check if they have ops
|
|
name_vars_to_ops = defaultdict(list)
|
|
for i, (v1, rel, v2) in enumerate(graph.triples):
|
|
if v1 in name_vars and rel.startswith(':op'):
|
|
name_vars_to_ops[v1].append((i, rel, v2.strip('"')))
|
|
|
|
triples = graph.triples.copy()
|
|
for nv, ops in name_vars_to_ops.items():
|
|
ops = sorted(ops, key=lambda x: int(x[1][3:]))
|
|
idx, _, lits = zip(*ops)
|
|
for i in idx:
|
|
triples[i] = None
|
|
|
|
lits = ['"' + l + '"' for lit in lits for l in lit.split('_')]
|
|
|
|
tt = []
|
|
for i, l in enumerate(lits, start=1):
|
|
rel = ':op' + str(i)
|
|
tt.append(penman.Triple(nv, rel, l))
|
|
|
|
triples[min(idx)] = tt
|
|
|
|
triples = [t if isinstance(t, list) else [t] for t in triples if t is not None]
|
|
triples = [t for tt in triples for t in tt]
|
|
|
|
graph_ = penman.Graph(triples)
|
|
graph_.metadata = graph.metadata
|
|
return graph_
|
|
|
|
|
|
def _reconstruct_graph_from_nodes(nodes, backreferences):
|
|
triples = []
|
|
triples_added = set()
|
|
|
|
variable2index = {}
|
|
index2variable = {}
|
|
start_index = 0
|
|
|
|
cnt = defaultdict(Counter)
|
|
|
|
while start_index < len(nodes):
|
|
stop_index = index_of('<stop>', nodes, default=len(nodes) + 1, start=start_index)
|
|
old_start_index = start_index
|
|
start_index = stop_index + 1
|
|
|
|
src_node, src_backr = nodes[old_start_index], backreferences[old_start_index]
|
|
|
|
if src_node == '<stop>':
|
|
continue
|
|
|
|
trg_nodes_edges = nodes[old_start_index:stop_index]
|
|
trg_nodes_edges_backr = backreferences[old_start_index:stop_index]
|
|
trg_nodes_edges_indices = list(range(old_start_index, stop_index))
|
|
|
|
if isinstance(src_node, str):
|
|
if src_node in ('<s>', '</s>', '<stop>'):
|
|
continue
|
|
elif ('/' in src_node) or (':' in src_node) or ('(' in src_node) or (')' in src_node):
|
|
src_node = 'thing'
|
|
|
|
if src_node is not None:
|
|
src_node = str(src_node)
|
|
src_var = src_node[0].lower()
|
|
if not src_var not in 'abcdefghijklmnopqrstuvwxyz':
|
|
src_var = 'x'
|
|
# src_var = f'{src_var}_{len(variable2index)}'
|
|
src_var = f'{src_var}{len(variable2index)}'
|
|
src_var_i = old_start_index
|
|
variable2index[src_var] = src_var_i
|
|
index2variable[src_var_i] = src_var
|
|
triple = penman.Triple(src_var, ':instance', src_node)
|
|
if triple not in triples_added:
|
|
triples.append(triple)
|
|
triples_added.add(triple)
|
|
else:
|
|
if src_backr in index2variable:
|
|
src_var = index2variable[src_backr]
|
|
# more resilient logic here
|
|
(trg_edges, trg_nodes), (_, trg_nodes_backr), (_, trg_nodes_indices) = \
|
|
separate_edges_nodes(
|
|
trg_nodes_edges,
|
|
trg_nodes_edges,
|
|
trg_nodes_edges_backr,
|
|
trg_nodes_edges_indices)
|
|
|
|
for n, e, nb, ni in zip(trg_nodes, trg_edges, trg_nodes_backr, trg_nodes_indices):
|
|
|
|
if isinstance(n, str) and n.startswith(':'):
|
|
continue
|
|
if isinstance(n, str) and n.startswith('<') and n.endswith('>'):
|
|
continue
|
|
if e == ':li':
|
|
pass
|
|
elif len(e) < 4 or (not e.startswith(':')):
|
|
continue
|
|
|
|
# same edge more than once
|
|
num = cnt[src_var][e]
|
|
# num = 0
|
|
if num:
|
|
|
|
if e.startswith(':op') or e.startswith(':snt'):
|
|
continue
|
|
# elif e.startswith(':ARG'):
|
|
# continue
|
|
elif num > 3:
|
|
continue
|
|
|
|
if n is None:
|
|
if nb not in index2variable:
|
|
continue
|
|
trg_var = index2variable[nb]
|
|
trg = trg_var
|
|
elif e == ':mode':
|
|
trg = n
|
|
elif (not isinstance(n, str)) or re.match(r"^[+-]?\d+\.?\d*$", n) or (n == '-') or (n == '+'):
|
|
trg = str(n)
|
|
elif (n.startswith('"') and n.endswith('"') and len(n) > 2):
|
|
trg = '"' + n.replace('"', '') + '"'
|
|
elif ('/' in n) or (':' in n) or ('(' in n) or (')' in n) or ('=' in n):
|
|
trg = f'"{n}"'
|
|
elif n == '"':
|
|
continue
|
|
elif (n.startswith('"') and (not n.endswith('"'))) or (not n.startswith('"') and (n.endswith('"'))) or (
|
|
'"' in n):
|
|
trg = '"' + n.replace('"', '') + '"'
|
|
else:
|
|
trg_var = n[0].lower()
|
|
if trg_var not in 'abcdefghijklmnopqrstuvwxyz':
|
|
trg_var = 'x'
|
|
# trg_var = f'{trg_var}_{len(variable2index)}'
|
|
trg_var = f'{trg_var}{len(variable2index)}'
|
|
trg_var_i = ni
|
|
variable2index[trg_var] = trg_var_i
|
|
index2variable[trg_var_i] = trg_var
|
|
triple = penman.Triple(trg_var, ':instance', n)
|
|
if triple not in triples_added:
|
|
triples.append(triple)
|
|
triples_added.add(triple)
|
|
trg = trg_var
|
|
|
|
triple = penman.Triple(src_var, e, trg)
|
|
if triple not in triples_added:
|
|
triples.append(triple)
|
|
triples_added.add(triple)
|
|
|
|
cnt[src_var][e] += 1
|
|
|
|
return penman.Graph(triples)
|
|
|
|
|
|
def build_graph(nodes, backreferences, restore_name_ops=False):
|
|
graph = _reconstruct_graph_from_nodes(nodes, backreferences)
|
|
if restore_name_ops:
|
|
graph = _split_name_ops(graph)
|
|
return graph
|
|
|
|
|
|
class ParsedStatus(enum.Enum):
|
|
OK = 0
|
|
FIXED = 1
|
|
BACKOFF = 2
|
|
|
|
|
|
def connect_graph_if_not_connected(graph):
|
|
try:
|
|
encoded = pm_encode(graph)
|
|
return graph, ParsedStatus.OK
|
|
except:
|
|
pass
|
|
|
|
nxgraph = nx.MultiGraph()
|
|
variables = graph.variables()
|
|
for v1, _, v2 in graph.triples:
|
|
if v1 in variables and v2 in variables:
|
|
nxgraph.add_edge(v1, v2)
|
|
elif v1 in variables:
|
|
nxgraph.add_edge(v1, v1)
|
|
|
|
triples = graph.triples.copy()
|
|
new_triples = []
|
|
addition = f'a{len(variables) + 1}'
|
|
triples.append(penman.Triple(addition, ':instance', 'and'))
|
|
for i, conn_set in enumerate(nx.connected_components(nxgraph), start=1):
|
|
edge = f':op{i}'
|
|
conn_set = sorted(conn_set, key=lambda x: int(x[1:]))
|
|
conn_set = [c for c in conn_set if c in variables]
|
|
node = conn_set[0]
|
|
new_triples.append(penman.Triple(addition, edge, node))
|
|
triples = new_triples + triples
|
|
metadata = graph.metadata
|
|
graph = penman.Graph(triples)
|
|
graph.metadata.update(metadata)
|
|
pm_encode(graph)
|
|
|
|
return graph, ParsedStatus.FIXED
|
|
|
|
|
|
def restore_backreferences_from_pointers(nodes):
|
|
new_nodes, new_backreferences = [], []
|
|
prev_pointer = None
|
|
pointer2i = {}
|
|
for n in nodes:
|
|
is_pointer = isinstance(n, str) and n.startswith('<pointer:') and n.endswith('>')
|
|
|
|
if not is_pointer:
|
|
if prev_pointer is not None:
|
|
if prev_pointer in pointer2i:
|
|
new_nodes.append(None)
|
|
new_backreferences.append(pointer2i[prev_pointer])
|
|
new_nodes.append(n)
|
|
new_backreferences.append(-1)
|
|
|
|
else:
|
|
pointer2i[prev_pointer] = len(new_nodes)
|
|
new_nodes.append(n)
|
|
new_backreferences.append(-1)
|
|
else:
|
|
new_nodes.append(n)
|
|
new_backreferences.append(-1)
|
|
|
|
prev_pointer = None
|
|
else:
|
|
prev_pointer = n
|
|
return new_nodes, new_backreferences
|