Does anyone know of an SVG parser written in Go? There is https://github.com/ajstarks/svgo but it looks like that that only generates SVG. I currently use the xml.NewDecoder and attempt to only read Path elements, but I would like to support all svg shapes
I tried writing one several months ago, but I started getting frustrated with the very loose syntax of styles and paths. My package was able to translate some basic SVG commands to calls to the draw2d package. It was for this that I wrote a simple gradient rendering package.
-Daniel
Does anyone know of an SVG parser written in Go? There is https://github.com/ajstarks/svgo but it looks like that that only generates SVG. I currently use the xml.NewDecoder and attempt to only read Path elements, but I would like to support all svg shapes
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
This is what I came up with for now, it's based on the javascript library canvg, any comments on if this could be organized differently to make it better Go code? Having and error return value instead of using panics everywhere would probably be more idiomatic// Used to decode xml data into a readable structtype Path struct {Style string `xml:"style,attr"`Data string `xml:"d,attr"`}// All supported Path Commandstype PathCommand intconst (NotAValidCommand PathCommand = iotaMoveToAbsoluteMoveToRelativeClosePathLineToAbsoluteLineToRelative)// PathCommand ToStringfunc (command PathCommand) String() string {switch command {case NotAValidCommand:return "NotAValidCommand"case MoveToAbsolute:return "MoveToAbsolute"case MoveToRelative:return "MoveToRelative"case ClosePath:return "ClosePath"case LineToAbsolute:return "LineToAbsolute"case LineToRelative:return "LineToRelative"}return "UNKNOWN"}// True if the given PathCommand is relativefunc (command PathCommand) IsRelative() bool {switch command {case MoveToRelative, LineToRelative:return truedefault:return false}panic("Not reachable")}// Convert string to command, returns NotAValidCommand if not validfunc ParseCommand(commandString string) PathCommand {switch commandString {case "M":return MoveToAbsolutecase "m":return MoveToRelativecase "Z":case "z":return ClosePathcase "L":return LineToAbsolutecase "l":return LineToRelativedefault:return NotAValidCommand}panic("Not reachable")}// Used to parse a path stringtype PathParser struct {// All of the tokens, strings could be numbers or commandstokens []string// The token the parser is currently attokenIndex int// The last PathCommand that was seencurrentCommand PathCommand// Track current position for relative movescurrentPosition Coordinate// The coordinates read for the pathcoordinates []Coordinate}// Create new parserfunc NewParser(originalPathData string) (parser *PathParser) {parser = &PathParser{}seperateLetters, _ := regexp.Compile(`([^\s])?([MmZzLlHhVvCcSsQqTtAa])([^\s])?`)seperateNumbers, _ := regexp.Compile(`([0-9])([+\-])`)pathData := seperateLetters.ReplaceAllString(originalPathData, "$1 $2 $3")pathData = seperateNumbers.ReplaceAllString(pathData, "$1 $2")pathData = strings.Replace(pathData, ",", " ", -1)parser.tokens = strings.Fields(pathData)parser.coordinates = make([]Coordinate, 0)return parser}// Parse the datafunc (this *PathParser) Parse() []Coordinate {for this.ReadCommand() {switch this.currentCommand {case MoveToAbsolute, MoveToRelative:this.ReadCoord(true)for this.PeekHasMoreArguments() { // can have multiple implicit line coordsthis.ReadCoord(false)}case LineToAbsolute, LineToRelative:for this.PeekHasMoreArguments() {this.ReadCoord(false)}default:panic(fmt.Sprint("Unsupported command:", this.currentCommand))}}return this.coordinates}// Move to next tokenfunc (this *PathParser) ReadCommand() bool {if this.tokenIndex >= len(this.tokens) {return false}commandString := this.tokens[this.tokenIndex]this.tokenIndex++this.currentCommand = ParseCommand(commandString)if this.currentCommand == NotAValidCommand {panic(fmt.Sprint("Unexpected command, saw ", commandString))}return true}// Return if the next token is a command or notfunc (this *PathParser) PeekHasMoreArguments() bool {if this.tokenIndex >= len(this.tokens) {return false}return ParseCommand(this.tokens[this.tokenIndex]) == NotAValidCommand}// Read two strings as a pair of doublesfunc (this *PathParser) ReadCoord(penUp bool) {if this.tokenIndex >= len(this.tokens)-1 {panic(fmt.Sprint("Not enough tokens to ReadCoord, at ", this.tokenIndex, " of ", len(this.tokens)))}number := this.tokens[this.tokenIndex]this.tokenIndex++x, err := strconv.ParseFloat(number, 64)if err != nil {panic(fmt.Sprint("Expected a parseable number, but saw", number, "which got parse error", err))}number = this.tokens[this.tokenIndex]this.tokenIndex++y, err := strconv.ParseFloat(number, 64)if err != nil {panic(fmt.Sprint("Expected a parseable number, but saw", number, "which got parse error", err))}if this.currentCommand.IsRelative() {x += this.currentPosition.Xy += this.currentPosition.Y}this.currentPosition = Coordinate{X: x, Y: y, PenUp: penUp}this.coordinates = append(this.coordinates, this.currentPosition)}// read a file and parse its Gcodefunc ParseSvgFile(fileName string) (data []Coordinate) {file, err := os.Open(fileName)if err != nil {panic(err)}data = make([]Coordinate, 0)decoder := xml.NewDecoder(file)for {t, _ := decoder.Token()if t == nil {break}switch se := t.(type) {case xml.StartElement:if se.Name.Local == "path" {var pathData Pathdecoder.DecodeElement(&pathData, &se)parser := NewParser(pathData.Data)data = append(data, parser.Parse()...)}}}if len(data) == 0 {panic("SVG contained no Path elements! Only Paths are supported")}return data}