PHP SVG editing

2019-07-11 05:11发布

问题:

For example I am trying to read the style block from an svg, I can get the type but not the string.

$svgTemplate = new SimpleXMLElement($_POST['SvgTemplateImport']);
$svgTemplate->registerXPathNamespace('svg', 'http://www.w3.org/2000/svg');
$svgTemplate->registerXPathNamespace('xlink', 'http://www.w3.org/1999/xlink');

//I tried a bunch of things and this how far I got
$test[] = $svgTemplate->xpath('/svg:svg/svg:style');
$test[] = $result[0][0]['type'];

var_dump($test);

How to retrieve the style string?
How to set the style string?
How to copy a group and change attributes on it? (like translate x and y)?

I also want to change the resolution and viewbox of the svg among other things, but I will be able to figure those out from the answer.

Thanks.

EDIT1: See my svg: I commented what I want to achieve.

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" width="460px" height="200px" viewBox="0 0 460 200" style="enable-background:new 0 0 460 200;" xml:space="preserve">
<style type="text/css">/* new style */</style>
<!-- I did cut the unrelevant parts out-->
<symbol id="MoonFull" viewBox="0 0 100 100">
    <path style="fill:#FFFFFF;" d="M50,99.33C22.8,99.33,0.67,77.2,0.67,50S22.8,0.67,50,0.67S99.33,22.8,99.33,50S77.2,99.33,50,99.33   z"/>
    <path style="fill:#231F20;" d="M50,1.35c26.83,0,48.65,21.82,48.65,48.65S76.83,98.65,50,98.65S1.35,76.83,1.35,50   S23.17,1.35,50,1.35 M50,0C22.39,0,0,22.39,0,50s22.39,50,50,50s50-22.39,50-50S77.61,0,50,0L50,0z"/>
</symbol>
<symbol id="Star" viewBox="0 0 100 100">
    <path id="Star" style="fill:#00FF00;" d="M50,3.58L62.5,39H100L70.83,60.95l10.42,34.93L50,74.81L18.75,95.53l10.42-34.51L0,39  h37.5L50,3.58z"/>
</symbol>
<g id="TemplateForm">
    <g id="twd">
        <rect id="twdCanvas" x="7.3" y="9.32" class="st0" width="263.03" height="72"/>
        <text id="twdWeekdayshorttext" transform="matrix(1 0 0 1 249.5817 41.9174)"><tspan x="0" y="0" class="st2 st3">C</tspan><tspan x="-1.2" y="29.13" class="st2 st3">S</tspan></text>
        <text id="twdDatenumber" transform="matrix(1 0 0 1 41.5544 69.5698)" class="st5 st6">26</text>
        <line id="twdDivider" class="st7" x1="126.49" y1="9.32" x2="126.49" y2="81.32"/>
        <g id="twdText">
            <text id="twdTextLine3" transform="matrix(1 0 0 1 135.0475 28.1718)" class="st2 st8">1234567890abcdef12345</text>
            <text id="twdTextLine2" transform="matrix(1 0 0 1 135.0475 41.5319)" class="st2 st8">1234567890abcdef12345</text>
            <text id="twdTextLine1" transform="matrix(1 0 0 1 135.0475 54.892)" class="st2 st8">1234567890abcdef12345</text>
            <text id="twdTextLine0" transform="matrix(1 0 0 1 135.0475 68.252)" class="st2 st8">1234567890abcdef12345</text>
        </g>
        <line id="twdDateNumAliignerLeft" class="st0" x1="120.02" y1="17.39" x2="120.02" y2="72.07"/>
        <rect id="twdBoxStar" x="15.68" y="19.75" class="st0" width="21" height="21"/>
        <rect id="twdBoxText" x="132.49" y="17.39" class="st0" width="107.92" height="54.68"/>
        <rect id="twdBoxMoon" x="15.68" y="49.15" class="st0" width="21" height="21"/>
    </g>
    <g id="tcl">
        <rect id="tclCanvas" x="7.3" y="153.31" class="st0" width="263.03" height="36"/>
        <line id="tclDivider" class="st7" x1="126.49" y1="153.31" x2="126.49" y2="189.31"/>
        <text id="tclYear" transform="matrix(1 0 0 1 59.1393 183.5269)" class="st2 st9">YYYY</text>
        <text id="tclMonthname" transform="matrix(1 0 0 1 130.9372 182.7798)" class="st2 st10">monthname</text>
    </g>
</g>
<g id="result"><!-- this group will be generated -->
    <!-- this is made from: #twd and added transformation-->
    <g id="result1" transform="matrix(1 0 0 1 249.5817 41.9174)">
        <rect id="twdCanvas" x="7.3" y="9.32" class="st0" width="263.03" height="72"/>
        <!-- all the texts will be canged -->
        <text id="twdWeekdayshorttext" transform="matrix(1 0 0 1 249.5817 41.9174)"><tspan x="0" y="0" class="st2 st3">C</tspan><tspan x="-1.2" y="29.13" class="st2 st3">S</tspan></text>
        <text id="twdDatenumber" transform="matrix(1 0 0 1 41.5544 69.5698)" class="st5 st6">26</text>
        <line id="twdDivider" class="st7" x1="126.49" y1="9.32" x2="126.49" y2="81.32"/>
        <g id="twdText">
            <text id="twdTextLine3" transform="matrix(1 0 0 1 135.0475 28.1718)" class="st2 st8">changed this</text>
            <text id="twdTextLine2" transform="matrix(1 0 0 1 135.0475 41.5319)" class="st2 st8">changed this</text>
            <text id="twdTextLine1" transform="matrix(1 0 0 1 135.0475 54.892)" class="st2 st8">changed this</text>
            <text id="twdTextLine0" transform="matrix(1 0 0 1 135.0475 68.252)" class="st2 st8">changed this</text>
        </g>
        <line id="twdDateNumAliignerLeft" class="st0" x1="120.02" y1="17.39" x2="120.02" y2="72.07"/>
        <rect id="twdBoxText" x="132.49" y="17.39" class="st0" width="107.92" height="54.68"/>

        <!-- this: <rect id="twdBoxStar" x="15.68" y="19.75" class="st0" width="21" height="21"/> to: -->
        <use x="132.49" y="17.39" width="21" height="21" xlink:href="#Star" /><!-- how can I chnage the fill color? -->

        <!-- this: <rect id="twdBoxMoon" x="15.68" y="49.15" class="st0" width="21" height="21"/> to: -->
        <use x="15.68" y="49.15" width="21" height="21" xlink:href="#MoonFull" />
    </g>
    <!-- and so on... the end I have to adjust the viewbox and resolution-->
</g>
</svg>

回答1:

Modifying Existing Structure

$svg_data = <<<'SVG'
<svg xmlns:svg="http://www.w3.org/2000/svg">
  <svg:style>
    circle { fill: orange; stroke: black; }
  </svg:style>
  <svg:g id="group-a" stroke="green" fill="white" stroke-width="5">
     <circle cx="25" cy="25" r="15"/>
     <circle cx="40" cy="25" r="15"/>
  </svg:g>
  <svg:g id="group-b" stroke="red" fill="blue" stroke-width="1">
     <circle cx="10" cy="80" r="15"/>
  </svg:g>
</svg>
SVG;

$svgTemplate = new SimpleXMLElement($svg_data);
$svgTemplate->registerXPathNamespace('svg', 'http://www.w3.org/2000/svg');

// How to retrieve the style string
$style = $svgTemplate->xpath('svg:style');
printf("Original style:\n%s\n\n", (string)$style[0]);

// How to set the style string
$children = $svgTemplate->children('svg', true);
$children->style = '/* new style */';

// How to copy a group and change attributes on it (like translate x and y)
$group_a = $svgTemplate->xpath('svg:g[@id="group-a"]');
if (isset($group_a[0])) {
  $group_a[0]->addAttribute('transform', 'translate(30) rotate(45 50 50)');
}

printf("Modified XML:\n%s\n", $svgTemplate->asXML());

Output

Original style:

    circle { fill: orange; stroke: black; }


Modified XML:
<?xml version="1.0"?>
<svg xmlns:svg="http://www.w3.org/2000/svg">
  <svg:style>/* new style */</svg:style>
  <svg:g id="group-a" stroke="green" fill="white" stroke-width="5" transform="translate(30) rotate(45 50 50)">
     <circle cx="25" cy="25" r="15"/>
     <circle cx="40" cy="25" r="15"/>
  </svg:g>
  <svg:g id="group-b" stroke="red" fill="blue" stroke-width="1">
     <circle cx="10" cy="80" r="15"/>
  </svg:g>
</svg>

Explanations

The node values are fetched by means of casting of the SimpleXMLElement object to string. This is possible because SimpleXMLElement implements __toString method.

Since the children method returns the node objects by reference, you can modify the original structure by modifying the value returned by this method. Note, you should pass the registered namespace to the children method when appropriate.

In order to add an attribute you need to fetch the node (using xpath method, for instance) and call addAttribute method in context of this node.

The methods of SimpleXMLElement (xpath, children, and attributes, for instance) return a set of matching elements as SimpleXMLElement objects. You can access them either by index (e.g. $style[0]), or by name ($children->style). You can also iterate them using foreach.

Cloning, Modifying, and Appending the Nodes

As far as I can see, it is impossible to append a SimpleXMLElement node to the document without doing the deep copy manually. You can make a deep copy by iterating the nodes recursively and calling the addChild method. But there is an easy way to perform this using the DOM extension:

$svg = new SimpleXMLElement($svg_data);
$svg->registerXPathNamespace('svg', 'http://www.w3.org/2000/svg');

$g_tpl = $svg->xpath('svg:g[@id="TemplateForm"]');
if (!isset($g_tpl[0])) {
  die("#TemplateForm group not found\n");
}

$g_res = clone $g_tpl[0];
$g_res['id'] = 'result';

$g_twd = $g_res->g[0];
$g_twd['id'] = 'result1';
$g_twd['transform'] = 'matrix(1 0 0 1 249.5817 41.9174)';

// Change the rest of the nodes in similar manner.
// Then add $g_res to the root node. You will need DOM extension for this.
$dom_res = dom_import_simplexml($g_res);
$dom_svg = dom_import_simplexml($svg);
$dom_svg->appendChild($dom_res);

echo $svg->asXML();

Consider Using DOM Extension

Generally, if you need to perform complex DOM manipulations on relatively small documents (such as SVG), use DOM extension instead of SimpleXML.

Example

$doc = new DOMDocument;
$doc->loadXML($svg_data);
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('svg', 'http://www.w3.org/2000/svg');

$g_res = $doc->createElement('g');
$g_res->setAttribute('id', 'result');

$g_twd_list = $xpath->query('svg:g[@id="TemplateForm"]/svg:g[@id="twd"]');

$g_twd = clone $g_twd_list[0];
$g_twd->setAttribute('id', 'result1');
$g_twd->setAttribute('transform', 'matrix(1 0 0 1 249.5817 41.9174)');
$g_res->appendChild($g_twd);

$doc->firstChild->appendChild($g_res);
echo $doc->saveXML();