From e17f2f3ee6ec930ad5fd78164849c8870ab40a28 Mon Sep 17 00:00:00 2001 From: Matthew Peveler Date: Sun, 17 Oct 2021 15:33:19 -1000 Subject: Improve handling escaped attributes inside macro targets Signed-off-by: Matthew Peveler --- asciidoc/asciidoc.py | 28 ++++++++++++++++------------ tests/test_asciidoc.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 tests/test_asciidoc.py diff --git a/asciidoc/asciidoc.py b/asciidoc/asciidoc.py index 4c323f1..f23d22b 100644 --- a/asciidoc/asciidoc.py +++ b/asciidoc/asciidoc.py @@ -780,7 +780,7 @@ def system(name, args, is_macro=False, attrs=None): return result -def subs_attrs(lines, dictionary=None): +def subs_attrs(lines, dictionary=None, ignore_skipped=False): """Substitute 'lines' of text with attributes from the global document.attributes dictionary and from 'dictionary' ('dictionary' entries take precedence). Return a tuple of the substituted lines. 'lines' @@ -829,7 +829,7 @@ def subs_attrs(lines, dictionary=None): if v is None: del dictionary[k] else: - v = subs_attrs(str(v)) + v = subs_attrs(str(v), ignore_skipped=(dictionary.get('name', None) is not None and k == 'target')) if v is None: del dictionary[k] else: @@ -838,6 +838,7 @@ def subs_attrs(lines, dictionary=None): # Substitute all attributes in all lines. result = [] for line in lines: + ignore_skipped_line = ignore_skipped # Make it easier for regular expressions. line = line.replace('\\{', '{\\') line = line.replace('\\}', '}\\') @@ -849,13 +850,16 @@ def subs_attrs(lines, dictionary=None): mo = reo.search(line, pos) if not mo: break - s = attrs.get(mo.group('name')) + attr_name = mo.group('name') + s = attrs.get(attr_name) if s is None: pos = mo.end() else: s = str(s) line = line[:mo.start()] + s + line[mo.end():] pos = mo.start() + len(s) + if attr_name == 'target' and dictionary.get('name', None) is not None and re.search(r'{[0-9]+%?}', line) is None: + ignore_skipped_line = True # Expand conditional attributes. # Single name -- higher precedence. reo1 = re.compile( @@ -869,6 +873,7 @@ def subs_attrs(lines, dictionary=None): r'(?P\=|\?|!|#|%|@|\$)' r'(?P.*?)\}(?!\\)' ) + force_skip = False for reo in [reo1, reo2]: pos = 0 while True: @@ -908,7 +913,7 @@ def subs_attrs(lines, dictionary=None): # mo.end() not good enough because '{x={y}}' matches '{x={y}'. end = end_brace(line, mo.start()) rval = line[mo.start('value'):end - 1] - UNDEFINED = '{zzzzz}' + s = '' if lval is None: if op == '=': s = rval @@ -917,11 +922,11 @@ def subs_attrs(lines, dictionary=None): elif op == '!': s = rval elif op == '#': - s = UNDEFINED # So the line is dropped. + force_skip = True elif op == '%': s = rval elif op in ('@', '$'): - s = UNDEFINED # So the line is dropped. + force_skip = True else: assert False, 'illegal attribute: %s' % attr else: @@ -934,7 +939,7 @@ def subs_attrs(lines, dictionary=None): elif op == '#': s = rval elif op == '%': - s = UNDEFINED # So the line is dropped. + force_skip = True elif op in ('@', '$'): v = re.split(r'(?$:} s = v[1] elif v[1] == '': # {$::} - s = UNDEFINED # So the line is dropped. + force_skip = True else: # {$::} s = v[1] else: if len(v) == 2: # {$:} - s = UNDEFINED # So the line is dropped. + force_skip = True else: # {$::} s = v[2] else: @@ -972,9 +977,8 @@ def subs_attrs(lines, dictionary=None): s = str(s) line = line[:mo.start()] + s + line[end:] pos = mo.start() + len(s) - # Drop line if it contains unsubstituted {name} references. - skipped = re.search(r'(?s)\{[^\\\W][-\w]*?\}(?!\\)', line) - if skipped: + # Drop line if it contains unsubstituted {name} references. + if force_skip or (not ignore_skipped_line and re.search(r'(?s)\{[^\\\W][-\w]*?\}(?!\\)', line) is not None): trace('dropped line', line) continue # Expand system attributes (eval has precedence). diff --git a/tests/test_asciidoc.py b/tests/test_asciidoc.py new file mode 100644 index 0000000..33f298a --- /dev/null +++ b/tests/test_asciidoc.py @@ -0,0 +1,39 @@ +from asciidoc import asciidoc +import io +import pytest + + +@pytest.mark.parametrize( + "input,expected", + ( + ( + '\\{attach}file.txt', + '

{attach}file.txt

\r\n' + ), + ( + 'link:\\{attach}file.txt[file]', + '

' + + 'file

\r\n' + ), + ( + 'image:\\{attach}file.jpg[]', + '

\r\n' + + '{attach}file.jpg\r\n' + + '

\r\n' + ), + ( + 'image:\\{attach}file.jpg[foo]', + '

\r\n' + + 'foo\r\n

\r\n' + ), + ) +) +def test_ignore_attribute(input, expected): + infile = io.StringIO(input) + outfile = io.StringIO() + options = [ + ('--out-file', outfile), + ('--no-header-footer', '') + ] + asciidoc.execute('asciidoc', options, [infile]) + assert outfile.getvalue() == expected -- cgit v1.2.1