<?php
    
// Written by Evgeny Stepanischev aka BOLK (http://bolknote.ru), 2009 (Public Domain License)

    
$_ENV getopt('DCIFr::f::');
    if (!
is_numeric($_ENV['r']) || $_ENV['r'] < 0$_ENV['r'] = 1;

    if (!isset(
$_ENV['f']) || $_ENV['f'] === false) {
        
$help file_get_contents(__FILE__nullnull__COMPILER_HALT_OFFSET__);

        
$trans = array(
            
'»' => '"',
            
'«' => '"',
            
'—' => '--',
        );

        if (
strtolower(substr(PHP_OS03)) == 'win')
        
$help iconv('utf-8''cp866//IGNORE'strtr($help$trans));

        die(
$help);
    }

    
// стили по умолчанию, их можно удалить без вреда
    
$toKill = array(
        
"opacity:1(?:\.0+)?",
        
"color:#(?:000000|000|black)",

        
"fill\-opacity:1(?:\.0+)?",
        
"fill\-rule:nonzero",

        
"stroke\-width:1(?:\.0+)?(?:px)?",
        
"stroke\-linecap:butt",
        
"stroke\-linejoin:miter",
        
"stroke\-miterlimit:4(?:\.0+)?",
        
"stroke\-dasharray:none",
        
"stroke\-dashoffset:0(?:\.0+)?",
        
"stroke\-opacity:1(?:\.0+)?",

        
"marker:none",
        
"marker\-(?:start|end|mid):none",

        
"visibility:visible",
        
"display:inline",
        
"overflow:visible",
        
"enable\-background:accumulate"
    
);

    
$doc = new DOMDocument;

    
$doc->preserveWhiteSpace $doc->formatOutput = isset($_ENV['F']);

    
$doc->Load($_ENV['f'], LIBXML_NOBLANKS LIBXML_NONET LIBXML_NSCLEAN);
    
$doc->normalizeDocument();

    
// тег metadata не несёт пользы для отображения
    
killNodes($doc->getElementsByTagName('metadata'));

    
// тег perspective тоже нужен только Inkscape
    
killNodes($doc->getElementsByTagNameNS('http://www.inkscape.org/namespaces/inkscape''perspective'));

    
// тег namedview пространства sodipodi нам бесполезен
    
killNodes($doc->getElementsByTagNameNS('http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd''namedview'));
    
    
// если defs оказался пустым, то удаляем и его
    
$toremove = array();

    foreach (
$doc->getElementsByTagName('defs') as $entry) {
        if (!
$entry->hasChildNodes()) {
            
$toremove[] = $entry;
        }
    }
    
killNodes($toremove);

    
// подготавливаем данные о стилях — переводим из удобного формата в регулярку
    
$toKill '/^' implode('|'$toKill) . '$/sui';

    
// раз уж мы сейчас пройдём по всем тегам, то соберём сразу все ID, которые присвоены тегам
    // и все ID, которые реально используются в inline-стилях
    
$ids $idsinline = array();

    
// это массив объектов тегов, которые можно впоследствии удалить
    
$toremove = array();

    
// у всех тегов оптимизирует атрибут style
    
foreach ($doc->getElementsByTagName('*') as $entry) {

        
// эти атрибуты добавляет программа Inkspace, нам они так же не нужны
        // http://wiki.inkscape.org/wiki/index.php/Inkscape_SVG_vs._plain_SVG
        
removeUselessAttr($entry, array(
            
'collect',
            
'label',
            
'groupmode',
            
'type',
            
'nodetypes',
            
'tiled-clone-of',
            
'tile-cx',
            
'tile-cy',
            
'tile-h',
            
'tile-w',
            
'connector-avoid',
            
'connector-type',
            
'connector-start',
            
'connector-end',
        ));

        if (
$entry->tagName == 'svg') {
            
removeUselessAttr($entry, array(
                
'output_extension',
                
'docname',
                
'docbase',
                
'version',
                
'modified',
            ));
        }
        elseif (
$entry->tagName == 'path') {
            
removeUselessAttr($entry, array(
                
'original',
                
'radius',
                
'type',
                
'cx''cy''rx''ry',
                
'argument''expansion''revolution''t0''flatsided''randomized',
                
'rounded''arg1''arg2''sides',
            ));

            if (
$entry->hasAttribute('d')) {

                
$attr trim($entry->getAttribute('d'));

                
// теги path с пустым d ничего не отображают
                
if ($attr == '') {
                    
$toremove[] = $entry;
                    continue;
                } else {
                    
// снижаем точность чисел и записываем их более компактно
                    
$entry->setAttribute('d'compactD($attr));
                }
            }
        }

        if (
$entry->hasAttribute('style')) {
            
$nondef = array();

            
$killFill $killStroke false;

            
$styles preg_split('!\s*;\s*!u'$entry->getAttribute('style'), -1PREG_SPLIT_NO_EMPTY);

            
// если у fill или strike дефолтные значения, то помечаем на удаление соответствующие стили
            
foreach ($styles as $style) {
                if (
$killFill && $killStroke) break;

                if (!
$killFill   && preg_match('/^fill\s*:\s*none$/siu',   $style)) $killFill true;
                if (!
$killStroke && preg_match('/^stroke\s*:\s*none$/siu'$style)) $killStroke true;
            }

            
// удаляем дефолтные и бесполезные стили
            
foreach ($styles as $style) {
                
// удаляем пробельные символы между именем и значением
                
$style preg_replace('/^([^:]+)\s*:\s* /su''\\1:'$style);

                if (
                    (!
$killStroke || strpos($style'stroke-') !== 0) &&
                    (!
$killFill || strpos($style'fill-') !== 0) &&
                    !
preg_match($toKill$style) ||

                    isset(
$_ENV['D'])
                ) {

                    
// пишем цвет в более компактной форме
                    
if (!isset($_ENV['C']))
                    
$style preg_replace(
                        
'/^(fill|stroke|stop-color|flood-color|lighting-color):#(.)\\2(.)\\3(.)\\4/su',
                        
'\\1:#\\2\\3\\4',
                        
$style
                    
);
                    
$nondef[] = $style;

                    if (
preg_match('!^[^:]+:url\(#([^\)]+)\)!sui'$style$m)) $idsinline[$m[1]] = null;
                }
            }

            
// заодно исчезнут пробельные символы между стилевыми правилами
            
$entry->setAttribute('style'implode(';'$nondef));
        }

        if (
$entry->hasAttribute('id')) {
            
$ids[$entry->getAttribute('id')] = $entry;
        }

        
// у номерных свойств урезаем незначащие нули
        
foreach ($entry->attributes as $attr) {
            if (
$attr->name <> 'id' && $attr->name <> 'd') {
                if (
is_numeric($attr->value)) {
                    
// числовые атрибуты могут не содержать нуля перед точкой
                    
$entry->setAttribute($attr->nameltrim((float) $attr->value'0'));
                } else {
                    
$entry->setAttribute($attr->namepreg_replace('/\b(?<!\#)\d+\.\d+\b/sue''(float) \\0'$attr->value));
                }
            }
        }

        if (
$entry->hasAttributeNS('http://www.w3.org/1999/xlink''href')) {
            
$idsinline[substr($entry->getAttributeNS('http://www.w3.org/1999/xlink''href'), 1)] = null;
        }

        if (
$entry->hasAttribute('clip-path')) {
            
$value $entry->getAttribute('clip-path');
            if (
preg_match('/^url\(#([^\)]+)\)$/isu'$value$m)) {
                
$idsinline[$m[1]] = null;
            }
        }
    }

    
// внутри цикла перебора не удаётся удалить ноду, это известное поведение модуля DOM
    
killNodes($toremove);

    
// мы можем проделать эту операцию удаления лишних ID, только если в SVG нет скриптов и таблиц стилей —
    // ID могут быть использованы там
    
if (!isset($_ENV['I']) && !$doc->getElementsByTagName('script')->length && !$doc->getElementsByTagName('style')->length) {
        
// удаляем все ID, которые не были использованы как ссылки
        
foreach (array_diff_key($ids$idsinline) as $entry) if ($entry) {
            
$entry->removeAttribute('id');
        }
    }

    
// и вычищаем все ненужные пространства имён
    
foreach ($doc->getElementsByTagName('svg') as $entry) {
        
$uselessNS = array(
            
'dc'  => 'http://purl.org/dc/elements/1.1/',
            
'cc'  => 'http://creativecommons.org/ns#',
            
'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
            
'sodipodi' => 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
            
'inkscape' => 'http://www.inkscape.org/namespaces/inkscape',
        );

        foreach (
$uselessNS as $ns => $url)
        
$entry->removeAttributeNS($url$ns);
    }

    
// удаляем комментарии и виндовые переводы строк
    
echo preg_replace('/\n?<!--.*?-->\n?/su'''str_replace("\r"''$doc->saveXML()));

    
// простые регулярные выражения для более компактной записи атрибута «d» для тега path
    
function compactD($str)
    {
        
$patterns = array(
            
'/\d+\.\d{2,}/e',
            
'/(\D)\s+(\d)/s',
            
'/(\d+)\s+(\D+)/s',
            
'/(\d+)\s+\-(\d+)/s',
        );

        
$replacements = array(
            
'round(\\0, $_ENV["r"])',
            
'$1$2',
            
'$1$2',
            
'$1$2',
        );

        return 
preg_replace($patterns$replacements$str);
    }

    
// удаление нод
    
function killNodes($nodes)
    {
        foreach (
$nodes as $entry)
        
$entry->parentNode->removeChild($entry);
    }

    
// удаление атрибутов ненужных пространств
    
function removeUselessAttr($entry, array $names) {
        foreach (
$names as $name) {
            
$entry->removeAttributeNS('http://www.inkscape.org/namespaces/inkscape'$name);
            
$entry->removeAttributeNS('http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'$name);
        }
    }

__halt_compiler();
Использованиеsvgcrush [ОПЦИИ] -fФАЙЛ РЕЗУЛЬТИРУЮЩИЙ_ФАЙЛ

Утилита для оптимизации размера файла SVG
.

-
fФАЙЛ          файлкоторый будет обработан
-r1,-r2,..,-r9  точность чисел в атрибуте «d» тега «path» (по умолчанию — 1)
-
D              не удалять атрибуты со значениями по умолчанию (по умолчанию — включено)
-
C              не оптимизировать значения цветов (по умолчанию — оптимизировать)
-
I              не удалять неиспользуемые ID (по умолчанию — удалить)
-
F              форматировать XML (по умолчанию — нет)

Начальные исследования и часть методов оптимизации — Jos Hirth (http://kaioa.com/node/35), февраль 2007
Автор — Евгений Степанищев (http://bolknote.ru), июль 2009 год