替换 SVG 文档中的变量(在 YAML 中外部定义)

Posted

技术标签:

【中文标题】替换 SVG 文档中的变量(在 YAML 中外部定义)【英文标题】:Replace variables in an SVG document (externally defined in YAML) 【发布时间】:2018-02-08 20:50:10 【问题描述】:

背景

一些资源讨论了在 SVG 文档中使用变量,包括:

Variables in SVG: Is it possible? SVG variable text How do I define or reference a variable in SVG? SVG: About using <defs> and <use> with variable text values http://www.schepers.cc/w3c/svg/params/ref.html https://www.w3.org/TR/2009/WD-SVGParamPrimer-20090430/#Introduction

虽然基于 CSS、javascript 和 HTML 的解决方案非常适合 Web,但在其他情况下 SVG 也很有用,并且能够为变量定义外部源同样方便。

问题

SVG 不提供一种机制来定义 SVG 相关的软件包(例如 Inkscape 和 rsvg-convert)可以重用的可重用文本。例如,以下将是极好的:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg ...>
<input href="definitions.svg" />
...
<text ...>$variableName</text>
</svg>

image element 可以重载以导入外部文件,但它是 hackish 并且不允许将文本值分配给变量名以供重复使用。

问题

您将如何从服务器上的外部文件(例如,YAML 文件,但也可以是数据库)读取变量名称和值,并在渲染之前替换 SVG 文件中的这些变量?

【问题讨论】:

正确的 XML 解决方案是使用 XSLT 来执行替换。从 YAML 创建 XSLT 模板听起来有点令人生畏,但至少你应该涵盖所有奇怪的极端情况。 使用 XSLT 将 XML 转换为 YAML 是 straightforward。通过可应用于文本节点和属性值的 XSLT 将 YAML 转码为 XML 字典需要大量工作。即使是 expanding 和 tool chain 从 YAML 转换为 XML 也需要额外的数据转换步骤,而使用简单的字符串替换可以避免这一步骤。如果有一个解决方案可以将 YAML 读入 XSLT 并且不需要花费数小时来实现,请告诉。 【参考方案1】:

另一种可能的解决方案:

在 Inkscape 中设置 对象属性

保存,我们会有类似的东西

...
<text
   xml:space="preserve"
   style="font-style:normal;font-weight:normal;font-size:60px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;image-rendering:auto"
   x="262.91638"
   y="86.339157"
   id="mytest"
   sodipodi:linespacing="125%"
   inkscape:label="#myvar"><desc
     id="desc4150">The test object to replace with a var</desc><title
     id="title4148">myobj</title><tspan
     sodipodi:role="line"
     id="tspan4804"
     x="262.91638"
     y="86.339157"
     style="fill:#ffffff">sample</tspan></text>
...

然后使用键值对创建 yaml 文件

myvar: hello world

并解析 SVG 并替换值

#! /usr/bin/env python

import sys
from xml.dom import minidom
import yaml

yvars = yaml.load(file('drawing.yaml', 'r'))
xmldoc = minidom.parse('drawing.svg')
for s in xmldoc.getElementsByTagName('text'):
    for c in s.getElementsByTagName('tspan'):
        c.firstChild.replaceWholeText(yvars[s.attributes['inkscape:label'].value[1:]])
print xmldoc.toxml()

这些值将被替换

<text id="mytest" inkscape:label="#myvar" sodipodi:linespacing="125%" style="font-style:normal;font-weight:normal;font-size:60px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;image-rendering:auto" x="262.91638" xml:space="preserve" y="86.339157"><desc id="desc4150">The test object to replace with a var</desc><title id="title4148">myobj</title>
    <tspan id="tspan4804" sodipodi:role="line" style="fill:#ffffff" x="262.91638" y="86.339157">hello world</tspan></text>

【讨论】:

谢谢!这适用于所有类型的文本节点(假设存在其他类型)?所提出的问题是关于更改文本,但主题是关于变量,这意味着能够替换 SVG 文件其他部分中的变量(例如,颜色、大小、矩阵变换参数、贝塞尔点等)。例如,如果文本不在tspan 中,则必须修改 Python 代码。有一个通用的解决方案会很好。 这只是一个示例,如果您需要更改其他标签或属性来扩展脚本。【参考方案2】:

我见过的一种方法是使用 Jinja 模板在将 Postscript 文件转换为 PDF 之前对其进行自定义。

你可以使用同样的方法。

将您的 SVG 文本文件作为 Jinja 模板,并将您的变量放在 YAML 中。

使用 Python 加载 Jinja 模板,然后应用在 YAML 文件中找到的变量

【讨论】:

如果您希望 SVG 文件可维护并保持 SVG 语法的清晰性,则此解决方案 +1。【参考方案3】:

一种可能的解决方案使用以下方法:

How can I parse a YAML file from a Linux shell script?(见bash script) rpl package(大多数发行版都可以在没有正则表达式的情况下替换字符串) bash grep、sed 和 awk

以下脚本:

    以 YAML 格式读取变量定义。 循环遍历当前目录中的所有文件。 检测文件是否定义了任何变量。 替换所有变量定义的值。 运行 Inkscape 将 SVG 文件转换为 PDF。

可以进行许多改进,但对于希望使用 YAML 在 SVG 文档中执行基本变量替换且依赖最少的人来说,这应该是一个好的开始。

没有执行任何卫生措施,因此在运行此脚本之前确保输入是干净的。

#!/bin/bash

COMMAND="inkscape -z"

DEFINITIONS=../variables.yaml

# Parses YAML files.
#
# Courtesy of https://***.com/a/21189044/59087
function parse_yaml 
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
   awk -F$fs '
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) if (i > indent) delete vname[i]
      if (length($3) > 0) 
         vn=""; for (i=0; i<indent; i++) vn=(vn)(vname[i])("_")
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
      
   '


# Load variable definitions into this environment.
eval $(parse_yaml $DEFINITIONS )

for i in *.svg; do
  INPUT=$i
  OUTPUT=$i

  # Replace strings in the file with values from the variable definitions.
  REPLACE_INPUT=tmp-$INPUT

  echo "Converting $INPUT..."

  # Subsitute if there's at least one match.
  if grep -q -o -m 1 -h  \$.* $INPUT; then
    cp $INPUT $REPLACE_INPUT

    # Loop over all the definitions in the file.
    for svgVar in $(grep -oh \$.* $INPUT); do
      # Strip off $ to get the variable name and then the value.
      varName=$svgVar:2:-1
      varValue=$!varName

      # Substitute the variable name for its value.
      rpl -fi "$svgVar" "$varValue" $REPLACE_INPUT > /dev/null 2>&1
    done

    INPUT=$REPLACE_INPUT
  fi

  $COMMAND $INPUT -A m_k_i_v_$OUTPUT.pdf
  rm -f $REPLACE_INPUT
done

通过对 SVG 文档执行一般搜索和替换,无需对脚本进行维护。此外,变量可以在文件中的任何位置定义,而不仅仅是在text 块内。

【讨论】:

对于缺乏原生 YAML 支持的基于 bash 的解决方法很有帮助

以上是关于替换 SVG 文档中的变量(在 YAML 中外部定义)的主要内容,如果未能解决你的问题,请参考以下文章

接口自动化测试:yaml文件中变量替换

Prometheus yaml 文件中的变量替换

带有外部和嵌入式 yaml 的 RMarkdown

Google Cloud Build 不会替换 cloudbuild.yaml 的机密部分中的值

外部 SVG 文档可以包含 AngularJS ng-click 或 ng-model 指令吗?

使用 POI 读取 Word docx 中的书签替换书签内容(汉字或合并外部文档内容)