PlantUML代码自动编码为SVG图片url展示

更新于2024-11-05 08:00:0017 分钟8 千字186732400
摘要

背景

最近发新文章,想画类图,习惯了用PlantUML,但是发现目前博客还不支持渲染PlantUML,于是想要开发支持一波~~

PlantUML Image

PlantUML简介

PlantUML是一个开源项目,它允许用户使用简单的文本描述语言来生成各种类型的 UML 图表。通过简单的文本描述,你可以创建类图、时序图、用例图、活动图等等。PlantUML 使用简单易懂的语法,让用户可以专注于图表的设计,而不必担心复杂的细节和语法。

PlantUML 的语法是基于文本的,你只需使用类似于代码的语法来描述你想要创建的图表,然后 PlantUML 会将其转换为相应的图像。这种文本描述的方式使得版本控制和团队协作变得非常简单,因为你只需管理文本文件而不是图像文件。

plantuml url生成规则

PlantUML 的 URL 生成规则如下:

  1. 基础 URL:PlantUML 的基础 URL 通常是 http://www.plantuml.com/plantuml/
  2. 文件类型:在基础 URL 后面加上文件类型(例如 pngsvg 等),以指定生成的图像类型。
  3. 编码方式:在文件类型后面,如果使用了编码方式(例如 deflatehuffman),需要在编码数据之前加上相应的编码标识。如果使用的是 Huffman 编码,需要在编码数据之前加上 ~1,如果使用的是 DEFLATE 编码,需要在编码数据之前加上 ~D
  4. PlantUML 代码:在编码标识后面,添加经过编码的 PlantUML 代码。

举例来说,如果你要生成 PNG 格式的 PlantUML 图像,并且使用 Huffman 编码,URL 的格式如下:

http://www.plantuml.com/plantuml/png/~1[经过编码的PlantUML代码]

如果使用的是 DEFLATE 编码,则需要将 ~1 替换为 ~D

DEFLATE 和 Huffman

DEFLATE 和 Huffman 是指对 PlantUML 代码进行的压缩编码方式。

  • DEFLATE 编码:DEFLATE 是一种常用的数据压缩算法,它结合了 LZ77 算法和哈夫曼编码。在 PlantUML 中,如果使用 DEFLATE 编码,生成的 URL 会以 ~D 开头,表示压缩后的数据使用 DEFLATE 算法进行解压缩。
  • Huffman 编码:Huffman 编码是一种用于数据压缩的算法,它根据数据的频率来构建变长编码,使得频率高的字符使用较短的编码,从而实现数据的压缩。在 PlantUML 中,如果使用 Huffman 编码,生成的 URL 会以 ~1 开头,表示压缩后的数据使用 Huffman 编码进行解压缩。

这些编码方式通常用于将 PlantUML 代码转换为 URL,以便在网络上传输或嵌入到网页中。通过压缩和编码,可以减小数据的大小,从而提高传输效率。

DEFLATE 和 Huffman 编码各有优劣:

  1. DEFLATE 编码
    • 优点:DEFLATE 是一种通用且高效的压缩算法,它通常能够提供更好的压缩比。对于大型的 PlantUML 图,使用 DEFLATE 可能会生成更小的文件大小。
    • 缺点:DEFLATE 编码的计算成本较高,需要更多的计算资源来进行压缩和解压缩。此外,DEFLATE 编码生成的 URL 可能会稍微长一些。
  2. Huffman 编码
    • 优点:Huffman 编码相对简单,生成的 URL 通常会比较短。如果你更关注 URL 的长度,或者需要在长度限制较严格的环境中使用,Huffman 编码可能更适合。
    • 缺点:Huffman 编码可能会导致稍微较大的文件大小,因为它通常不如 DEFLATE 在压缩效率上表现得那么好。

因此,要选择哪种编码方式取决于你更看重的因素:是压缩效率还是 URL 长度。在大多数情况下,DEFLATE 编码可能是更好的选择,因为它提供了更好的压缩效率,尽管 URL 可能会略长一些。

编码规则

原则

例如下面的uml文本描述:

@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
@enduml

编码

Syp9J4vLqBLJSCfFib9mB2t9ICqhoKnEBCdCprC8IYqiJIqkuGBAAUW2rO0LOr5LN92VLvpA1G00

要实现这样的编码,文本图是:

  1. 以 UTF-8 编码
  2. 使用Deflate算法压缩
  3. 使用接近Base64 的转换以 ASCII 重新编码

为什么不使用 Base64?

主要原因是历史性的:这种格式最初并不是为了公开而创建的。现在想改变已经太晚了。但是,唯一的区别在于字符顺序。

在 base64 中,值 0-63 的映射数组是:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

对于 PlantUML,值 0-63 的映射数组为:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_

代码实现

本文给出python、java和typescript三种实现方案,供参考

pyhton

def encode_plantuml(plantuml_text):
    # Encode text in UTF-8
    utf8_encoded = plantuml_text.encode('utf-8')
    # Compress using Deflate algorithm
    compressed = zlib.compress(utf8_encoded)
    # Reencode in ASCII using a transformation close to base64
    reencoded = base64.b64encode(compressed)
    # Convert reencoded to a string if it's not already
    # Convert reencoded to a string if it's not already
    if isinstance(reencoded, dict):
        reencoded_str = ''.join(reencoded.values())
    else:
        reencoded_str = reencoded

    # Replace base64 characters according to PlantUML mapping array
    encoded_plantuml = reencoded_str.translate(bytes.maketrans(
        b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
        b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
    ))

    return encoded_plantuml.decode('utf-8')

# Example usage:
plantuml_text = '''
@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
@enduml
'''
encoded_plantuml = encode_plantuml(plantuml_text)
print(encoded_plantuml)

Java

package com.izhxxx.utils;

import java.util.Base64;
import java.util.zip.Deflater;

/**
 * @author :zhanghang(izhxxx@163.com)
 */
public class PlantUmlUtilTest {
    public static String encodePlantUml(String plantumlText) {
        // Encode text in UTF-8
        byte[] utf8Encoded = plantumlText.getBytes();
        // Compress using Deflate algorithm
        Deflater deflater = new Deflater();
        deflater.setInput(utf8Encoded);
        deflater.finish();
        byte[] compressed = new byte[utf8Encoded.length];
        int compressedLength = deflater.deflate(compressed);
        deflater.end();
        // Reencode in base64
        byte[] compressedData = new byte[compressedLength];
        System.arraycopy(compressed, 0, compressedData, 0, compressedLength);
        byte[] reencoded = Base64.getEncoder().encode(compressedData);

        String reencodedString = new String(reencoded);
        return translateBase64(reencodedString);
    }
    private static String translateBase64(String reencodedString) {
        // Define mapping arrays
        char[] base64Mapping = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
        char[] plantUmlMapping = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".toCharArray();

        StringBuilder translated = new StringBuilder();
        for (char c : reencodedString.toCharArray()) {
            // Find index of character in base64 mapping
            int index = indexOf(base64Mapping, c);
            // If character found in base64 mapping, replace it with corresponding character from PlantUML mapping
            if (index != -1) {
                translated.append(plantUmlMapping[index]);
            } else {
                // If character not found in base64 mapping, keep it as it is
                translated.append(c);
            }
        }
        return translated.toString();
    }

    // Helper method to find index of character in array
    private static int indexOf(char[] array, char c) {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == c) {
                return i;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        // Example usage
        String plantUMLText = "@startuml\n" +
                "Alice -> Bob: Authentication Request\n" +
                "Bob --> Alice: Authentication Response\n" +
                "@enduml";
        String encodedPlantUML = encodePlantUml(plantUMLText);
        System.out.println(encodedPlantUML);
    }
}

typescript

import pako from 'pako'

function encodePlantUML(plantumlText: string): string {
  // Encode text in UTF-8
  const utf8Encoded: Uint8Array = new TextEncoder().encode(plantumlText)
  console.log('utf8Encoded: ', Array.from(utf8Encoded))
  // Compress using Deflate algorithm
  const compressed: Uint8Array = pako.deflate(utf8Encoded)
  console.log('compressed: ', Array.from(compressed))
  // Reencode in base64
  const reencoded: string = btoa(
    String.fromCharCode.apply(null, Array.from(compressed))
  )
  // Replace base64 characters according to PlantUML mapping array
  return reencoded.split('').map(translateBase64).join('')
}

function translateBase64(char: string): string {
  const base64Mapping =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  const plantUmlMapping =
    '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'

  const index = base64Mapping.indexOf(char)
  if (index !== -1) {
    return plantUmlMapping[index]
  }
  return char
}

function calcPlantUmlSvgUrl(plantumlText: string): string {
  const encode: string = encodePlantUML(plantumlText)
  return `https://www.plantuml.com/plantuml/svg/~1${encode}`
}

export default calcPlantUmlSvgUrl

plantuml官方文档

https://plantuml.com/zh/text-encoding

仓库地址

代码开源在github

https://github.com/izhxxx/zh-plantuml-image

评论区

你认为这篇文章怎么样?
  • great
    0
  • happy
    0
  • doubt
    0
  • boring
    0
  • bad
    0

0/2048