使用C#解析SVG“path”元素 - 有没有库可以做到这一点?

时间:2011-02-25 09:05:58

标签: c# wpf xml svg

我正在用C#编写一个基本上读取SVG文件的程序,并对内容做了一些有用的事情。我将使用的最复杂的数据是路径。他们采取如下形式:

<path d="M5.4,3.806h6.336v43.276h20.738v5.256H5.4V3.806z"/>

在这种情况下,M,h,v,H,V和z表示一些命令。在某种程度上,它们就像函数一样,跟随它们的数字是参数。还有一些更复杂的问题:

<path d="M70.491,50.826c-2.232,1.152-6.913,2.304-12.817,2.304c-13.682,0-23.906-8.641-23.906-24.626
        c0-15.266,10.297-25.49,25.346-25.49c5.977,0,9.865,1.296,11.521,2.16l-1.584,5.112C66.747,9.134,63.363,8.27,59.33,8.27
        c-11.377,0-18.938,7.272-18.938,20.018c0,11.953,6.841,19.514,18.578,19.514c3.888,0,7.777-0.792,10.297-2.016L70.491,50.826z"/>

在这种情况下,“c”命令后跟6个参数(第一种情况下为-2.232,1.152,-6.913,2.304,-12.817和2.304)。你可以看到这可能会变得棘手。我的问题是:SO社区是否知道任何现有的库将这些数据读入一些有用的ADT?

在我编写所有内容并编写大量字符串解析函数之前,我真的不想重新发明轮子。此外,任何建议将不胜感激。我知道如何阅读XML文档,这不是问题所在。

2 个答案:

答案 0 :(得分:9)

我不知道c#中的特定库,但是你可以从解析这种结构开始:

string path = "M5.4,3.806h6.336v43.276h20.738v5.256H5.4V3.806z";
string separators = @"(?=[MZLHVCSQTAmzlhvcsqta])"; // these letters are valid SVG
                             // commands. Whenever we find one, a new command is 
                             // starting. Let's split the string there.
var tokens = Regex.Split(path, separators).Where(t => !string.IsNullOrEmpty(t));

现在你有一个命令列表,后跟参数。然后,您可以以相同的方式继续拆分参数。

你说参数可以用空格,逗号或减号分隔(与逗号和空格不同,它应该是参数的一部分),所以你可以使用另一个简单的正则表达式(注意我'我不喜欢正则表达式,但在这种情况下,我认为它们增加了可读性。)

string argSeparators = @"[\s,]|(?=-)"; // discard whitespace and comma but keep the -
var splitArgs = Regex
   .Split(remainingargs, argSeparators)
   .Where(t => !string.IsNullOrEmpty(t)); 

我会将它包装在一个SVGCommand类中,就像这个

一样
class SVGCommand
{
    public char command {get; private set;}
    public float[] arguments {get; private set;}

    public SVGCommand(char command, params float[] arguments)
    {
        this.command=command;
        this.arguments=arguments;
    }

    public static SVGCommand Parse(string SVGpathstring)
    {
        var cmd = SVGpathstring.Take(1).Single();
        string remainingargs = SVGpathstring.Substring(1);

        string argSeparators = @"[\s,]|(?=-)";
        var splitArgs = Regex
            .Split(remainingargs, argSeparators)
            .Where(t => !string.IsNullOrEmpty(t));

        float[] floatArgs = splitArgs.Select(arg => float.Parse(arg)).ToArray();
        return new SVGCommand(cmd,floatArgs);
    }
}

现在一个简单的“解释器”看起来像这样:

    string path = "M70.491,50.826c-2.232,1.152-6.913,2.304-12.817,2.304c-13.682,0-23.906-8.641-23.906-24.626" +
"c0-15.266,10.297-25.49,25.346-25.49c5.977,0,9.865,1.296,11.521,2.16l-1.584,5.112C66.747,9.134,63.363,8.27,59.33,8.27" +
"c-11.377,0-18.938,7.272-18.938,20.018c0,11.953,6.841,19.514,18.578,19.514c3.888,0,7.777-0.792,10.297-2.016L70.491,50.826z";
    string separators = @"(?=[A-Za-z])";
    var tokens = Regex.Split(path, separators).Where(t => !string.IsNullOrEmpty(t));

    // our "interpreter". Runs the list of commands and does something for each of them.
    foreach (string token in tokens){
                    // note that Parse could throw an exception
                    // if the path is not correct 
        SVGCommand c = SVGCommand.Parse(token);
        Console.WriteLine("doing something with command {0}", c.command);
    }

如果你需要做一些更复杂的事情,F#可能是better suited for the job(并且可以与C#互操作)。我并不是建议只为这个特定的任务学习F#,我只是想我会提到它,以防你已经在考虑其他事情了。

答案 1 :(得分:4)

可以使用WPF Geometry对象执行此操作。据我所知,WPF使用的Path Markup syntax与SVG路径的语法相同。

var data = "M5.4,3.806h6.336v43.276h20.738v5.256H5.4V3.806z";

var geometry = Geometry.Parse(data);

var pathGeometry = PathGeometry.CreateFromGeometry(geometry);

foreach (var figure in pathGeometry.Figures)
{
    // Do something interesting with each path figure.
    foreach (var segment in figure.Segments)
    {
        // Do something interesting with each segment.
    }
}