diff --git a/.gitchangelog.rc b/.gitchangelog.rc index f086e31..bc77a69 100644 --- a/.gitchangelog.rc +++ b/.gitchangelog.rc @@ -63,6 +63,7 @@ ignore_regexps = [ r'@wip', r'!wip', r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', + r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[t|T]est:', r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', r'^[Uu]pdating .*poms .*$', r'^[mM]erging .*$', @@ -79,15 +80,15 @@ ignore_regexps = [ ## section_regexps = [ ('New', [ - r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + r'^[nN]ew\s*:\s*((dev|use?r|pkg|doc)\s*:\s*)?([^\n]*)$', r'^[aA]dded\s+', ]), ('Changes', [ - r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + r'^[cC]hg\s*:\s*((dev|use?r|pkg|doc)\s*:\s*)?([^\n]*)$', r'^[cC]hange[\w]\s+.*$', ]), ('Fix', [ - r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + r'^[fF]ix\s*:\s*((dev|use?r|pkg|doc)\s*:\s*)?([^\n]*)$', r'\b[fF]ix(ed|es)?\b', r'\b[cC]orrect\w*\b', ]), diff --git a/CHANGELOG.md b/CHANGELOG.md index d2dc053..fa94e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,22 @@ # Changelog -## development (2020-03-04) +## 3.3.0 (2020-06-18) + +### New + +* Added support for loading plantuml from external files (refs #42) [Chalmela, Ravi] + +### Changes + +* Updated documentation. [Michele Tessaro] + +### Fix + +* Fixed closing of object tag (fixes #44) [Michele Tessaro] + + +## 3.2.2 (2020-03-04) ### Fix @@ -51,10 +66,6 @@ ## 3.1.3 (2019-08-26) -### New - -* Added a test for issue #31. [Michele Tessaro] - ### Changes * Updated changelog for the new release. [Michele Tessaro] @@ -97,10 +108,6 @@ ## 3.1.1 (2019-05-29) -### New - -* Configured travis to test with multiple Markdown versions. [Michele Tessaro] - ### Fix * Fixed compatibility with Markdown 2 (refs #29) [Michele Tessaro] @@ -144,10 +151,6 @@ ## 2.0.2 (2019-03-16) -### New - -* Added test to verify utf-8 character handling. [Michele Tessaro] - ### Changes * Updated documentation for new release. [Michele Tessaro] @@ -167,8 +170,6 @@ * Fixed package contents. [Michele Tessaro] -* Fixed travis configuration. [Michele Tessaro] - ## 2.0.0 (2019-03-02) diff --git a/README.md b/README.md index 871fcec..874e19b 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ plantuml_markdown: server: http://www.plantuml.com/plantuml # PlantUML server, for remote rendering # other global options cachedir: /tmp # set a non-empty value to enable caching + base_dir: . # where to search for diagrams to include format: png # default diagram image format classes: class1,class2 # default diagram classes title: UML diagram # default title (tooltip) for diagram images @@ -167,6 +168,7 @@ The plugin has several configuration option: * `server`: PlantUML server url, for remote rendering. Defaults to `''`, use local command * `cachedir`: directory for caching of diagrams. Defaults to `''`, no caching * `priority`: extension priority. Higher values means the extension is applied sooner than others. Defaults to `30` +* `base_dir`: path where to search for external diagrams files For passing options to the `plantuml_plugin` see the documentation of the tool you are using. diff --git a/plantuml_markdown.py b/plantuml_markdown.py index f44ad2d..d4cd179 100644 --- a/plantuml_markdown.py +++ b/plantuml_markdown.py @@ -83,6 +83,7 @@ class PlantUMLPreprocessor(markdown.preprocessors.Preprocessor): \s*(title=(?P"|')(?P.*?)(?P=quot3))? \s*(width=(?P<quot4>"|')(?P<width>[\w\s"']+%?)(?P=quot4))? \s*(height=(?P<quot5>"|')(?P<height>[\w\s"']+%?)(?P=quot5))? + \s*(source=(?P<quot6>"|')(?P<source>.*?)(?P=quot6))? \s*\n (?P<code>.*?)(?<=\n) \s*::end-uml::[ ]*$ @@ -98,6 +99,7 @@ class PlantUMLPreprocessor(markdown.preprocessors.Preprocessor): \s*(title=(?P<quot3>"|')(?P<title>.*?)(?P=quot3))? \s*(width=(?P<quot4>"|')(?P<width>[\w\s"']+%?)(?P=quot4))? \s*(height=(?P<quot5>"|')(?P<height>[\w\s"']+%?)(?P=quot5))? + \s*(source=(?P<quot6>"|')(?P<source>.*?)(?P=quot6))? [ ]* }?[ ]*\n # Optional closing } (?P<code>.*?)(?<=\n) @@ -134,6 +136,8 @@ def _replace_block(self, text): title = m.group('title') if m.group('title') else self.config['title'] width = m.group('width') if m.group('width') else None height = m.group('height') if m.group('height') else None + source = m.group('source') if m.group('source') else None + base_dir = self.config['base_dir'] if self.config['base_dir'] else None # Convert image type in PlantUML image format if img_format == 'png': @@ -146,9 +150,17 @@ def _replace_block(self, text): # logger.error("Bad uml image format '"+imgformat+"', using png") requested_format = "png" - # Extract diagram source end convert it - code = m.group('code') + if source and base_dir: + # Load diagram source from external file + with open(os.path.join(base_dir, source), 'r') as f: + code = f.read() + else: + # Extract diagram source from markdown text + code = m.group('code') + + # Extract diagram source end convert it (if not external) diagram = self._render_diagram(code, requested_format) + self_closed = True # tags are always self closing if img_format == 'txt': # logger.debug(diagram) @@ -173,6 +185,7 @@ def _replace_block(self, text): data = 'data:image/svg+xml;base64,{0}'.format(base64.b64encode(diagram).decode('ascii')) img = etree.Element('object') img.attrib['data'] = data + self_closed = False # object tag must be explicitly closed else: # png format, explicitly set or as a default when format is not recognized data = 'data:image/png;base64,{0}'.format(base64.b64encode(diagram).decode('ascii')) img = etree.Element('img') @@ -195,7 +208,8 @@ def _replace_block(self, text): img.attrib['alt'] = alt img.attrib['title'] = title - return text[:m.start()] + etree.tostring(img).decode() + text[m.end():], True + return text[:m.start()] + etree.tostring(img, short_empty_elements=self_closed).decode() \ + + text[m.end():], True def _render_diagram(self, code, requested_format): cached_diagram_file = None @@ -258,7 +272,8 @@ def __init__(self, **kwargs): 'server': ["", "PlantUML server url, for remote rendering. Defaults to '', use local command."], 'cachedir': ["", "Directory for caching of diagrams. Defaults to '', no caching"], 'priority': ["30", "Extension priority. Higher values means the extension is applied sooner than others. " - "Defaults to 30"] + "Defaults to 30"], + 'base_dir': [".", "Base directory for external files inclusion"] } # Fix to make links navigable in SVG diagrams diff --git a/setup.py b/setup.py index 9a40cfe..89437d3 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setuptools.setup( name="plantuml-markdown", - version="3.2.2", + version="3.3.0", author="Michele Tessaro", author_email="michele.tessaro@email.it", description="A PlantUML plugin for Markdown", diff --git a/test/data/include_output.html b/test/data/include_output.html new file mode 100644 index 0000000..53435c0 --- /dev/null +++ b/test/data/include_output.html @@ -0,0 +1 @@ +<p><img alt="uml diagram" class="uml" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE4AAABvCAIAAADNOGHZAAAAKXRFWHRjb3B5bGVmdABHZW5lcmF0ZWQgYnkgaHR0cDovL3BsYW50dW1sLmNvbREwORwAAAECelRYdHBsYW50dW1sAAB4nEWOy26DMBBF9yPxD7NMFkYG0Yp6UaV5VaWgRIWwrUxwkSWwKz9Q8/d1G0XZ3jvnzllZx43z0xgBRvCChDzjOoIIVkL1/zEcR67cqSpxFsZKrTCJU5rkMU0XeyOx4gbpEyY5SynLMtzsGvzrl7B4PZZotTdngb20zsjOuzCwhILPHD+8cnISDA/fQhXb91uAOzVLo9UklIOire4HjxlZS4e1MEEF2+q60161WPAKUp9plpKO5hAgw51UA9YX68TEsJTK/8ChvhMPcRZTYnnHL1rBVnxxP7rw/6z7ADI8NXuSQ8nV4PkQTKWDjQ6W5sLwrYFfMFlhTXahMB4AAAQ0SURBVHja7ZtfSFNRHMe3TB8ytKJZKhIk9KAPkg+ZlkpIiH+j6CGCKMpKKNAn/4E6kEnCVJTeJNoUxpzW1pIc1JwmThmu9SChe/EfYv5JXVNn/u1XN0Scebfde8/V7ffjMI7Dc7/3c849556dc76CLZ8Jga+jrs7/nDKYWUmrC3bamyAjtzcqlFEKLrGSpjvNtKhk5PZDHayVThmUHqfBGqlbqFzL7YcK5Tc3v3icvnco3ULlWo5zVF1Ng9FotFgsVqt1YmLCZrPxJcc5qkpcrdVq9Xq9yWQC+ZmZGb7kOEdtKq5UKBRqtRrkobKhpvmS4xy1sUgik8lAHiobHq3h4WG+5DhHbS6XgrZcLlepVFDT8FDxJYeoiLqdtNo6gUDQ1lbPNapgRwQFBaanXx0a0hBFzcpKysm5mZ2dTACVyqyv98/OGgoK7sfEXCCHOjbWHh4e4nD0RUScgTwZVCotLfUGBx8nh1pe/rSw8AFkiosfQp4M6saGeW6uSyzOrarKI4S6ttYPjUl1GPiEPHxDpq9CCIVCubyCEKpGU5uUFLv9J+RhiCLWqvX1hVFR5wmhZmQk7qrpzMxEYn3Vbu8JCPAngToy8oEakLa/WV7uCwsTjY62k0GdnPwYGnqaBGpp6WPnUUEieV5W9oQA6vR0R0nJo/z8u5yjwvATHR1ps3Xv0pif/wz9x3lwYndYggFJJDoJnIuLRpwYIiqiIiqiIiqiIuqBQB2skUJ5jxO1ieI6KtdynO/EuY7Ktdx/9lcX7NOdZirpahpU4uqm4srGIglcyO30upEWlYwc/a650WjUarUKhULGIPZf3ScjR49qsVigetRqNZSXexS0ezZk5OhR4RkwmUxQEupJ5VHQ7sSRkaNHhYqBMlBD8DzonUKWW6KnC9r9VTJy9Kjw31A3UAyee6tT1PnHWukCCkJxuAhcamVlhS85pod5YHwneSCHiRyiIqpzDIgbSKIykfP5g3eIiqg+gupDwxIrL5ul8anx1o51xy+feK8CqjoktSst74dpwPunEEDb7HcZLqg5mzZQ8WrPRvae2RLQvj11XRVw5c/SiV+8IeXZrkb2qtkS0GpEqS3HEv8tFwnjdjYyn8MSaMMNUXfAVv5TQs7OlbHWwGTlkTjoyVMGM5NbPXDv1Yl3Xc1H4ynI9ug7bZG33p+78e2F3DE561VTCIqzNeia7uI9+Oy5XTSp6/XC2RJwqvwTILHVjAd0WJrp/vrmRAptM3rDbMn6ssWVZsRVCERFVPwRhz/NERVRvQMV15YOz8vGdfP330M45LzmTOTQa45ec/Sao9ccveZ8yaHXHM8DIyp6zdFr7i4qes3Ra45e8y30mqPXHL3m6DVHrznOgREVUREVUREVUdFrjl5z9Jqj15yUHHrNdwR6zd0IFr3mTOSYes1dCRa95kzk8IgWoh7q+A3YYrvPERmJhwAAAABJRU5ErkJggg==" title="" /></p> diff --git a/test/data/included_diag.puml b/test/data/included_diag.puml new file mode 100644 index 0000000..e504796 --- /dev/null +++ b/test/data/included_diag.puml @@ -0,0 +1,2 @@ + +A --> B diff --git a/test/markdown_builder.py b/test/markdown_builder.py index c232dc2..1280740 100644 --- a/test/markdown_builder.py +++ b/test/markdown_builder.py @@ -21,6 +21,7 @@ def _reset_diagram(self): self._diagram_buffer = "" self._width = "" self._height = "" + self._source = "" def _emit_diagram(self): """ @@ -29,7 +30,7 @@ def _emit_diagram(self): """ if self._diagram_buffer: delim = re.sub(r'(\W{3,})(\w+)', r'\1{\2', self._delimiter) if self._extended_syntax else self._delimiter - args = self._format + self._class + self._alt + self._title + self._width + self._height + args = self._format + self._class + self._alt + self._title + self._width + self._height + self._source self._buffer += (' '*self._indent)+delim+args+('}' if self._extended_syntax else '') self._buffer += "\n"+(' '*self._indent)+self._diagram_buffer+"\n"+(' '*self._indent)+self._end_delimiter+"\n" @@ -92,12 +93,21 @@ def width(self, w): def height(self, h): """ Define the maximum height of the diagram image. - :param w: Max width, with unit (ex: "120px") + :param h: Max width, with unit (ex: "120px") :return: The object itself """ self._height = " height='%s'" % h return self + def source(self, file_path): + """ + Sets inclusion of an external source diagram instead on an inline code. + :param file_path: Path of the file containing the diagram source + :return: The object itself + """ + self._source = " source='%s'" % file_path + return self + def text(self, txt): """ Adds a new text to the markdown source. diff --git a/test/test_plantuml.py b/test/test_plantuml.py index 2dca9ea..f588a40 100644 --- a/test/test_plantuml.py +++ b/test/test_plantuml.py @@ -26,7 +26,13 @@ def _load_file(self, filename): return f.read()[:-1] # skip the last newline FAKE_IMAGE = 'ABCDEF==' - IMAGE_REGEX = re.compile(r'<(?:img|.*object)(?:( alt=".*?")|( class=".*?")|( title=".*?")|( style=".*?")|( src=".*?")|(?:.*?))+/>') + IMAGE_REGEX = re.compile(r'<(?:img|.*object)' + r'(?:( alt=".*?")|' + r'( class=".*?")|' + r'( title=".*?")|' + r'( style=".*?")|' + r'( src=".*?")|' + r'(?:.*?))+(?:/>|></(?:img|.*object>))') BASE64_REGEX = re.compile( r'("data:image/[a-z+]+;base64,)(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?') @@ -46,7 +52,6 @@ def sort_attributes(groups): return cls.BASE64_REGEX.sub(r'\1%s' % cls.FAKE_IMAGE, html) return cls.IMAGE_REGEX.sub(lambda x: sort_attributes(x.groups()), html) - #return cls.BASE64_REGEX.sub(r'\1%s' % cls.FAKE_IMAGE, html) FAKE_SVG = '...svg-body...' SVG_REGEX = re.compile(r'<(?:\w+:)?svg(?:( alt=".*?")|( class=".*?")|( title=".*?")|( style=".*?")|(?:.*?))+>.*</(?:\w+:)?svg>') @@ -215,8 +220,11 @@ def test_arg_format_svg_object(self): Test for the correct parsing of the format argument, generating a svg image """ text = self.text_builder.diagram("A --> B").format("svg_object").build() + html = self.md.convert(text) self.assertEqual(self._stripImageData(self._load_file('svg_object_diag.html')), - self._stripImageData(self.md.convert(text))) + self._stripImageData(html)) + # verify that the tag is explicitly closed + self.assertIsNotNone(re.match(r'.*<object .*?></object>.*', html)) def test_arg_format_svg_inline(self): """ @@ -303,6 +311,23 @@ def test_arg_format_width_and_height_svg_inline(self): self.assertEqual(self._stripSvgData('<p><svg alt="uml diagram" title="" class="uml" style="max-width:120px;max-height:120px">...svg-body...</svg></p>'), self._stripSvgData(self.md.convert(text))) + def test_arg_source(self): + """ + Test for the correct parsing of the source argument + """ + include_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') + self.md = markdown.Markdown(extensions=['markdown.extensions.fenced_code', + 'pymdownx.snippets', 'plantuml_markdown'], + extension_configs={ + 'plantuml_markdown': { + 'base_dir': include_path + } + }) + + text = self.text_builder.diagram("ignored text").source("included_diag.puml").build() + self.assertEqual(self._stripImageData(self._load_file('include_output.html')), + self._stripImageData(self.md.convert(text))) + def test_multidiagram(self): """ Test for the definition of multiple diagrams on the same document