У меня есть приведенный ниже рисунок, сделанный из случайных фигур с различным количеством точек, к которому я могу добавить текстовые поля с помощью следующего XSLT.
Решение, предложенное в этой ветке (то есть x = "50%" y = "50%"
и dominant-baseline = "middle"
text-anchor = "middle"
), не работает, так как все такие текстовые поля оказываются в одном и том же положении рисунка, перекрываясь. Я действительно хотел бы, чтобы они были в центре каждого пути, в честь которого они названы. Вот рабочий пример, которая показывает поведение. Я уже спрашивал, можно ли этого добиться с помощью Javascript, но, поскольку преобразование будет выполняться с помощью макроса VBA, мне сказали, что это будет неправильным решением. По сути, мне нужно было бы заполнить поля x и y средней высотой и шириной путей, в которые должно вписываться каждое текстовое поле, эти держатели текста создаются в этой части кода:
<text x = "" y = "" id = "{$id}-text" style = "-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000 " dominant-baseline = "middle" text-anchor = "middle">
<tspan id = "{$id}-tspan" x = "" y = "">
<xsl:value-of select = "$id"/>
</tspan>
</text>
SVG
<svg xmlns = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" id = "exportSvg" width = "400" height = "400">
<defs/>
<rect width = "400" height = "400" transform = "translate(0, 0)" fill = "rgb(255, 255, 255)" style = "fill:rgb(255, 255, 255);"/>
<g>
<g id = "Drawing-svg" clip-path = "url(#rect-mask-Drawing)">
<clipPath id = "rect-mask-Drawing">
<rect x = "0" y = "0" width = "400" height = "400"/>
</clipPath>
<g id = "chart-svg">
<g id = "svg-main" clip-path = "url(#rect-mask-Main)">
<clipPath id = "rect-mask-Main">
<rect x = "0" y = "0" width = "400" height = "400"/>
</clipPath>
<g id = "Drawing-svg">
<g id = "Parts-svg">
<g id = "Section-svg">
<g id = "Item1-svg">
<path d = "M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z" stroke = "#000000" style = "fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id = "Item1"/>
</g>
<g id = "Item2-svg">
<path d = "M 198.06872,89.614437 -9.21291,31.643703 -23.42303,34.67823 51.52002,20.68699 47.20879,-57.62707 z" stroke = "#000000" style = "fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id = "Item2"/>
</g>
<g id = "Item3-svg">
<path d = "M 161.0455,182.56778 -41.68122,-5.64443 15.98375,27.05111 67.62172,3.73783 32.80201,-13.55927 z" stroke = "#000000" style = "fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id = "Item3"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
XSLT
<?xml version = "1.0" encoding = "utf-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
xmlns:svg = "http://www.w3.org/2000/svg"
xmlns = "http://www.w3.org/2000/svg"
exclude-result-prefixes = "svg"
version = "1.0">
<xsl:output method = "xml" encoding = "utf-8" omit-xml-declaration = "yes"/>
<xsl:strip-space elements = "*"/>
<xsl:template match = "svg:g[@id[starts-with(., 'Item')]]">
<xsl:copy>
<xsl:apply-templates select = "@* | node()"/>
<xsl:variable name = "id" select = "substring-before(@id, '-')"/>
<text x = "" y = "" id = "{$id}-text" style = "-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000 " dominant-baseline = "middle" text-anchor = "middle">
<tspan id = "{$id}-tspan" x = "" y = "">
<xsl:value-of select = "$id"/>
</tspan>
</text>
</xsl:copy>
</xsl:template>
<xsl:template match = "processing-instruction('xml-stylesheet')"/>
<xsl:template match = "@* | node()">
<xsl:copy>
<xsl:apply-templates select = "@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Следующие шаблоны преобразуют путь, например
<path d = "M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z"/>
в спецификацию его центра, например
<center x = "137.5" y = "122.7712355"/>
Они делают определенные предположения о запятых и пробелах в пути, но их можно легко адаптировать. Также они используют нестандартную функцию node-set
, которая поддерживается почти всеми процессорами XSLT 1.0, только под другими именами, например exslt:node-set
.
<xsl:template match = "path">
<xsl:copy-of select = "."/> <!-- copy the <path> element -->
<xsl:call-template name = "bbox">
<xsl:with-param name = "path" select = "substring-before(substring-after(@d,'M '),' Z')"/>
<xsl:with-param name = "bbox">
<bbox x = "10000" X = "-10000" y = "10000" Y = "-10000"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name = "bbox">
<xsl:param name = "path"/>
<xsl:param name = "bbox"/>
<xsl:variable name = "b" select = "node-set($bbox)/*"/>
<xsl:choose>
<xsl:when test = "$path">
<xsl:variable name = "x" select = "number(substring-before($path,','))"/>
<xsl:variable name = "y" select = "number(substring-after(substring-before($path,' '),','))"/>
<xsl:variable name = "next" select = "substring-after($path,' ')"/>
<xsl:call-template name = "bbox">
<xsl:with-param name = "path" select = "$next"/>
<xsl:with-param name = "bbox">
<bbox>
<xsl:attribute name = "x">
<xsl:choose>
<xsl:when test = "$x < $b/@x">
<xsl:value-of select = "$x"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "$b/@x"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name = "X">
<xsl:choose>
<xsl:when test = "$x > $b/@X">
<xsl:value-of select = "$x"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "$b/@X"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name = "y">
<xsl:choose>
<xsl:when test = "$y < $b/@y">
<xsl:value-of select = "$y"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "$b/@y"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name = "Y">
<xsl:choose>
<xsl:when test = "$y > $b/@Y">
<xsl:value-of select = "$y"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "$b/@Y"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</bbox>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<center x = "{($b/@x + $b/@X) div 2}" y = "{($b/@y + $b/@Y) div 2}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
В вашем конкретном случае замените элемент <center>
текстом, который вы хотите в этих координатах:
<xsl:variable name = "id" select = "substring-before(parent::g/@id,'-')"/>
<text x = "{($b/@x + $b/@X) div 2}" y = "{($b/@y + $b/@Y) div 2}" id = "{$id}-text">
<tspan id = "{$id}-tspan" x = "{($b/@x + $b/@X) div 2}" y = "{($b/@y + $b/@Y) div 2}">
<xsl:value-of select = "$id"/>
</tspan>
</text>
Замените элемент <center>
тем, что вы хотите вывести для данного элемента <path>
: возможно, путем и связанным текстовым элементом.
Я смиренно признаюсь, что не знаю, как... Я пробовал с <text match = "svg:g[@id[starts-with(., 'Item')]]" x = "{($c/@x + $c/@X) div 2}" y = "{($c/@y + $c/@Y) div 2}"/>
, но это не совсем правильно.
Смотрите мой дополненный ответ.
Я сделал попытку, ниже.
На самом деле это довольно сложно сделать в XSLT 1, потому что синтаксический анализ значения svg:path/@d
требует большой обработки строк и рекурсии.
Я написал именованный шаблон get-bounding-box-edge-value
, который вы вызываете с 2 параметрами; строка, содержащая список координат x,y и строку, указывающую, какое ребро вы хотите найти (либо «ВЕРХ», «НИЗ», «ЛЕВО» или «ПРАВО»). Шаблон рекурсивно вызывает сам себя для обработки списка и возвращает минимальную координату y для 'ВЕРХ', максимальную y для 'НИЗ', минимальную х для 'ЛЕВО' и максимальную х для 'ПРАВО'.
Затем при обработке svg:path
я вызываю этот шаблон 4 раза, чтобы получить 4 ребра, которые определяют ограничивающую рамку пути, вычислить центральную точку из этих 4 значений и поместить элемент svg:text
в эту точку, установив атрибуты dominant-baseline
и text-anchor
так что текстовое содержание svg:text
сосредоточено вокруг этой точки.
<?xml version = "1.0" encoding = "utf-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" xmlns:svg = "http://www.w3.org/2000/svg" xmlns = "http://www.w3.org/2000/svg" exclude-result-prefixes = "svg" version = "1.0">
<xsl:output method = "xml" encoding = "utf-8" omit-xml-declaration = "yes" indent = "yes"/>
<xsl:template name = "get-bounding-box-edge-value">
<xsl:param name = "which-edge"/>
<xsl:param name = "coordinate-path"/>
<xsl:variable name = "next-coordinate-pair" select = "substring-before($coordinate-path, ' ')"/>
<xsl:variable name = "remaining-coordinates" select = "substring-after($coordinate-path, ' ')"/>
<!-- get the next value (either an x or y coordinate) from the first of the list of coordinate pairs -->
<xsl:variable name = "next-value">
<xsl:choose>
<xsl:when test = "$which-edge='TOP' or $which-edge='BOTTOM'">
<!-- we want the Y coordinate -->
<xsl:value-of select = "substring-after($next-coordinate-pair, ',')"/>
</xsl:when>
<xsl:otherwise>
<!-- we want the X coordinate -->
<xsl:value-of select = "substring-before($next-coordinate-pair, ',')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test = "$remaining-coordinates">
<xsl:variable name = "remaining-edge-value">
<xsl:call-template name = "get-bounding-box-edge-value">
<xsl:with-param name = "which-edge" select = "$which-edge"/>
<xsl:with-param name = "coordinate-path" select = "$remaining-coordinates"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test = "$which-edge='TOP' or $which-edge='LEFT'">
<!-- we're calculating the minimum value (NB 0,0 is the upper-left corner) -->
<xsl:choose>
<xsl:when test = "number($next-value) < number($remaining-edge-value)">
<xsl:value-of select = "$next-value"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "$remaining-edge-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- we're calculating the maximum value -->
<xsl:choose>
<xsl:when test = "number($next-value) > number($remaining-edge-value)">
<xsl:value-of select = "$next-value"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "$remaining-edge-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- there are no more coordinates in the path - this is the last coordinate pair -->
<!-- so we just return the value taken from this coordinate pair -->
<xsl:value-of select = "$next-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match = "svg:g[starts-with(@id, 'Item')]">
<!-- calculate the bounding box of the path by extracting the TOP, LEFT, RIGHT, and BOTTOM coordinate value-->
<xsl:variable name = "coordinate-path" select = "translate(substring-after(svg:path/@d, 'M '), 'zZ', '')"/>
<xsl:variable name = "top">
<xsl:call-template name = "get-bounding-box-edge-value">
<xsl:with-param name = "which-edge">TOP</xsl:with-param>
<xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name = "left">
<xsl:call-template name = "get-bounding-box-edge-value">
<xsl:with-param name = "which-edge">LEFT</xsl:with-param>
<xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name = "bottom">
<xsl:call-template name = "get-bounding-box-edge-value">
<xsl:with-param name = "which-edge">BOTTOM</xsl:with-param>
<xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name = "right">
<xsl:call-template name = "get-bounding-box-edge-value">
<xsl:with-param name = "which-edge">RIGHT</xsl:with-param>
<xsl:with-param name = "coordinate-path" select = "$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<!-- calculate the coordinates of the centroid -->
<xsl:variable name = "center-x" select = "(number($left) + number($right)) div 2"/>
<xsl:variable name = "center-y" select = "(number($top) + number($bottom)) div 2"/>
<xsl:copy>
<xsl:apply-templates select = "@* | node()"/>
<xsl:variable name = "id" select = "substring-before(@id, '-')"/>
<text x = "50%" y = "50%" id = "{$id}-text" style = "
-inkscape-font-specification:'Calibri, Normal';
font-family:Calibri;font-weight:normal;font-style:normal;
font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;
font-variant-caps:normal;font-variant-numeric:normal;
font-variant-east-asian:normal;
fill:#000000;
text-align:center
">
<tspan id = "{$id}-tspan" x = "{$center-x}" y = "{$center-y}" dominant-baseline = "middle" text-anchor = "middle">
<xsl:value-of select = "$id"/>
</tspan>
</text>
</xsl:copy>
</xsl:template>
<xsl:template match = "@* | node()">
<xsl:copy>
<xsl:apply-templates select = "@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Обратите внимание, что это не будет анализировать какое-либо значение path/@d
в произвольном SVG, но он обрабатывает ваши входные данные, которые используют только команды M
(moveto) и Z
(закрыть) (и z
, что означает то же, что и Z
). Из вашего комментария о том, как генерируется SVG, я предполагаю, что это, вероятно, безопасно. Но для произвольных путей вам также нужно будет обрабатывать другие команды, такие как H
(горизонтальные линии), C
(кривые) и т. д., и этот именованный шаблон должен быть более сложным.
Спасибо, я на самом деле имел в виду всевозможные пути, совершенно произвольные и с кривыми Безье, но, поскольку в приведенном выше коде это было неясно, я принял ответ. В любом случае это полезно в случае полигонов, хотя в моем случае мне придется найти способ настроить его. Будет ли это намного сложнее?
Теперь я понимаю, что должен был потратить больше времени на размышления о том, как задать вопрос, есть много переменных, которые я не рассматривал как объяснение, должен ли я переформулировать или задать еще одну, если я не справлюсь?
Было бы немного сложнее, да. Сложность заключается в разборе этой @d
«командной» строки, потому что ее синтаксис имеет различные дополнительные функции; например вы можете сказать «M0,0 M0,1 Z» или вы можете сказать «M 0,0 0,1 z», и они означают одно и то же (вторая команда «M» избыточна, потому что она следует за предыдущей). Таким образом, этот именованный шаблон должен иметь еще один параметр для указания «текущей команды», чтобы он знал, какая команда выполняется в случае, если она не была указана явно, и еще два параметра для хранения «текущей позиции», чтобы он мог вычислить относительные координаты
(команды, указанные строчными буквами, используют относительные координаты, тогда как прописные буквы используют абсолютные координаты). Другие команды также имеют разное количество параметров; команды горизонтальной и вертикальной линии задают только одну точку, а команды Безье задают контрольные точки... я предполагаю, что этот именованный шаблон, вероятно, удвоится в размере.
спасибо, простите мое невежество, но как мне интегрировать это в вышеизложенное? Я пытался поместить до или после блока, создающего текстовые поля, но ничего не произошло, я также попытался создать другую таблицу стилей, которая преобразует файл, полученный в результате предыдущего преобразования.