Редактирование SVG через CSS-селекторы в C#

CSS-селекторы – практичный способ точно выбрать SVG-элементы перед редактированием. В Aspose.SVG for .NET C#-код может использовать QuerySelector() и QuerySelectorAll(), чтобы находить элементы по имени тега, иерархии, атрибутам или позиции в SVG-дереве, а затем изменять атрибуты, заменять узлы или обновлять данные пути.

В статье используется пример owl.svg. Примеры выбирают круги и пути в SVG DOM и создают отредактированные версии исходного векторного изображения.

Рабочий процесс редактирования через CSS-селекторы

Обычно редактирование через селекторы состоит из четырех шагов:

  1. Загрузить исходный SVG-файл в SVGDocument.
  2. Использовать QuerySelector() для одного элемента или QuerySelectorAll() для списка элементов.
  3. Изменить атрибуты, стили, текст, геометрию или данные пути выбранных элементов.
  4. Сохранить отредактированный SVG-документ.

Краткое введение в DOM-навигацию, XPath и фильтры узлов смотрите в статье Инспекция и навигация SVG в C#.

Выбор и редактирование SVG-элементов

Первый пример редактирует исходный файл owl.svg. Иллюстрация совы построена из элементов path и circle. Код использует CSS-селекторы, чтобы:

1using Aspose.Svg;
2using System.IO;
3using Aspose.Svg.Dom;
4using Aspose.Svg.Collections;
 1// Edit SVG elements with CSS selectors in C#
 2
 3// Load an SVG document
 4using (SVGDocument document = new SVGDocument(new Url("https://docs.aspose.com/svg/files/owl.svg")))
 5{
 6    // Get the root <svg> element of the document
 7    SVGSVGElement svgElement = document.RootElement;
 8
 9    // Find the first element that matches the specified in selector
10    SVGGElement gElement = svgElement.QuerySelector("g") as SVGGElement;
11
12    // Find all <circle> elements in a <g> element
13    NodeList circleNodes = gElement.QuerySelectorAll("circle");
14
15    // Make big blue eyes
16    foreach (Node circleNode in circleNodes)
17    {
18        SVGCircleElement circleElement = circleNode as SVGCircleElement;
19        circleElement.R.BaseVal.Value *= 1.5F;
20        circleElement.SetAttribute("stroke", "blue");
21    }
22
23    // Get a path for the owl's wing
24    SVGPathElement wingPath = gElement.QuerySelector("path:nth-child(2)") as SVGPathElement;
25
26    // Apply style attributes to the wing
27    wingPath.SetAttribute("stroke-width", "16");
28    wingPath.SetAttribute("stroke-dasharray", "2 8");
29
30    document.Save(Path.Combine(OutputDir, "owl-edited1.svg"));
31}

Отредактированный SVG-файл можно открыть здесь: owl-edited1.svg.

Замена выбранных SVG-элементов

Второй пример продолжает работу с тем же исходным файлом owl.svg. Он выбирает исходные круглые глаза и заменяет их квадратными:

  1. Создать новый элемент <g> для прямоугольников, которые заменят глаза.
  2. Использовать QuerySelectorAll("g:first-child > circle"), чтобы выбрать круги в первой группе.
  3. Создать элементы <rect> с нужными атрибутами размера и стиля.
  4. Вставить прямоугольники в новую группу.
  5. Удалить выбранные элементы <circle> из документа.
1using Aspose.Svg;
2using System.IO;
3using Aspose.Svg.Dom;
4using Aspose.Svg.Collections;
 1// Replace SVG circles with rectangles using CSS selectors in C#
 2
 3// Load an SVG document
 4using (SVGDocument document = new SVGDocument(new Url("https://docs.aspose.com/svg/files/owl.svg")))
 5{
 6    // Get the root <svg> element of the document
 7    SVGSVGElement svgElement = document.RootElement;
 8
 9    // Create a new <g> element with style attributes and append it as the last child of the <svg> element
10    SVGGElement gElement = (SVGGElement)document.CreateElementNS(SvgNamespace, "g");
11    gElement.SetAttribute("fill", "none");
12    gElement.SetAttribute("stroke-width", "2");
13    svgElement.AppendChild(gElement);
14
15    // Find all <circle> elements from the first <g> element
16    NodeList circleNodes = svgElement.QuerySelectorAll("g:first-child > circle");
17
18    // Make square sky-blue eyes
19    foreach (Node circleNode in circleNodes)
20    {
21        SVGCircleElement circleElement = circleNode as SVGCircleElement;
22
23        float cx = circleElement.Cx.BaseVal.Value;
24        float cy = circleElement.Cy.BaseVal.Value;
25        float r = circleElement.R.BaseVal.Value;
26
27        SVGRectElement rectElement = (SVGRectElement)document.CreateElementNS(SvgNamespace, "rect");
28        rectElement.X.BaseVal.Value = cx - r;
29        rectElement.Y.BaseVal.Value = cy - r;
30        rectElement.Width.BaseVal.Value = 3 * r;
31        rectElement.Height.BaseVal.Value = 3 * r;
32        rectElement.SetAttribute("stroke", "SkyBlue");
33
34        // Add a <rectangle> element into the second (new) <g> element and remove <circle> elements
35        gElement.AppendChild(rectElement);
36        circleElement.Remove();
37    }
38    // Recolor last rectangle in the second (new) <g> element
39    Element lastRect = gElement.LastElementChild;
40    lastRect.SetAttribute("stroke", "red");
41
42    document.Save(Path.Combine(OutputDir, "owl-svg-eyes.svg"));
43}

Выбор пути и изменение его данных

Финальная часть меняет крыло совы: вместо кривой используется ломаная линия, а цвет обновляется. Пример выбирает элемент path, изменяет данные пути и сохраняет результат:

1using Aspose.Svg;
2using System.IO;
3using Aspose.Svg.Dom;
4using Aspose.Svg.Collections;
 1// Modify SVG path data selected with CSS selectors in C#
 2
 3// Load an SVG document
 4using (SVGDocument document = new SVGDocument(Path.Combine(DataDir, "owl-svg-eyes.svg")))
 5{
 6    SVGSVGElement svgElement = document.RootElement;
 7
 8    // Get a path for owl's body from the first <g> element
 9    Element bodyPath = (svgElement.QuerySelector("g:first-child") as SVGGElement).FirstElementChild;
10    bodyPath.SetAttribute("stroke", "Teal");
11
12    // Get a path for the owl's wing from the first <g> element
13    SVGPathElement wingPath = svgElement.QuerySelector("g:first-child > path:nth-child(2)") as SVGPathElement;
14
15    // Form new wing path data based on the old
16    string d = "";
17    foreach (SVGPathSeg pathSeg in wingPath.PathSegList)
18    {
19        if (pathSeg is SVGPathSegMovetoAbs)
20        {
21            SVGPathSegMovetoAbs pathSegMovetoAbs = pathSeg as SVGPathSegMovetoAbs;
22
23            d += string.Format(" M {0} {1}", pathSegMovetoAbs.X, pathSegMovetoAbs.Y);
24        }
25        if (pathSeg is SVGPathSegCurvetoCubicAbs)
26        {
27            SVGPathSegCurvetoCubicAbs pathSegCurvetoCubicAbs = pathSeg as SVGPathSegCurvetoCubicAbs;
28
29            d += string.Format(
30                " L {0} {1} L {2} {3}",
31                (pathSegCurvetoCubicAbs.X1 + pathSegCurvetoCubicAbs.X2) / 2F,
32                (pathSegCurvetoCubicAbs.Y1 + pathSegCurvetoCubicAbs.Y2) / 2F,
33                pathSegCurvetoCubicAbs.X,
34                pathSegCurvetoCubicAbs.Y
35            );
36        }
37    }
38    // Set d attribute - new path data formation
39    wingPath.SetAttribute("d", d.Trim());
40    wingPath.SetAttribute("stroke", "Teal");
41
42    document.Save(Path.Combine(OutputDir, "owl-edited2.svg"));
43}

На изображении сравниваются исходная сова и две отредактированные версии.

Исходная SVG-иллюстрация совы и две версии после редактирования селекторами

Второй отредактированный SVG-файл можно открыть здесь: owl-edited2.svg.

FAQ

1. Когда использовать QuerySelector(), а когда QuerySelectorAll()?
Используйте QuerySelector(), когда нужен только первый подходящий элемент. Используйте QuerySelectorAll(), когда нужно проверить или изменить все подходящие элементы, например все круги внутри группы.

2. Могут ли CSS-селекторы сами изменять SVG-элементы?
Нет. Селекторы только находят элементы. После выбора элемента используйте DOM-методы и свойства, например SetAttribute(), AppendChild(), RemoveChild() или типизированные SVG-свойства, чтобы внести изменения.

3. Можно ли конвертировать SVG после редактирования через селекторы?
Да. После редактирования SVGDocument вызовите Converter.ConvertSVG(document, options, outputPath) с подходящими параметрами сохранения или сначала сохраните SVG-файл, а затем конвертируйте его.

Дальнейшие шаги