SmallSketch
about
ss one

How to get native ss one data from SVG that is exported from ss one

When you export your drawing from ss one to your Google Drive, you can get a SVG file. This SVG file contains not only SVG data but also native ss one data. In this post, I will discover the way to see and get this native ss one data.

Let's see a simplest example just one circle SVG (just-a-circle.svg):

the simplest just a cirle ss one SVG that contains native ss one data

This xml data as SVG content that I draw with ss one for this blog entry:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:ss="http://ns.smallsketch.app/SmallSketch/1.0" version="1.1" x="0.0mm" y="0.0mm" width="12.599613mm" height="11.248993mm" viewBox="0.0 0.0 12.599613 11.248993">
  <ss:strokes>
    <ss:stroke color="rgb(76, 96, 130)" strokewidth="0.254">5.0661163,0.9569702,5.047348,0.9475708,4.878479,0.881897,4.6627007,0.83499146,4.4938316,0.81622314,4.3249626,0.81622314,4.09042,0.81622314,3.7995872,0.8725281,3.452465,1.022644,3.0490494,1.229065,2.6081085,1.5480347,2.1577873,1.904541,1.735611,2.3267212,1.3509636,2.739563,1.0882759,3.133606,0.7974434,3.5276184,0.54413605,3.9873657,0.38464737,4.4564514,0.25330353,4.8880005,0.15010834,5.310211,0.06567001,5.7417603,0.009380341,6.1170654,0.0,6.5204773,0.0,6.9145203,0.056289673,7.3367004,0.15010834,7.758911,0.28144836,8.162323,0.44093704,8.546997,0.64733887,8.931641,1.0601273,9.325684,1.5010719,9.700958,1.942009,10.038727,2.3735695,10.329559,2.486145,10.357697,3.311737,10.817413,3.884018,11.051971,4.4563026,11.183319,5.1317863,11.248993,5.844795,11.248993,6.5390396,11.17395,7.233284,11.033203,7.9369125,10.85495,8.612392,10.686066,9.316021,10.479675,9.935215,10.2638855,10.460587,9.963654,10.957817,9.616516,11.464428,9.241241,11.886604,8.82843,12.149288,8.434387,12.327549,7.9653015,12.4401245,7.44928,12.524563,6.8769836,12.5808525,6.248413,12.599613,5.554138,12.599613,4.7754517,12.505798,4.0342407,12.290016,3.3681335,11.942894,2.777069,11.577011,2.2329102,11.211124,1.716919,10.770184,1.3041077,10.282337,0.9663696,9.775726,0.7224121,9.212822,0.4784851,8.678066,0.2626953,8.171459,0.13134766,7.6648407,0.046936035,7.1488533,0.0,6.614094,0.0,6.163769,0.05630493,5.769741,0.093811035,5.4132347,0.17828369,5.159931,0.32836914,4.8597145,0.4315796,4.606411,0.5253906,4.362484,0.6286011,4.1467056,0.7036438,3.9778366,0.7224121,3.855877,0.7411804,3.7995872,0.79748535,3.790203,0.83499146,3.884018,0.9100647,4.1185646,0.9475708,4.1185646,0.9475708</ss:stroke>
  </ss:strokes>
  <g stroke="rgb(76, 96, 130)" stroke-width="0.254" fill="none">
    <path d="M 5.0661163 0.9569702 L 5.047348 0.9475708 4.878479 0.881897 4.6627007 0.83499146 4.4938316 0.81622314 4.3249626 0.81622314 4.09042 0.81622314 3.7995872 0.8725281 3.452465 1.022644 3.0490494 1.229065 2.6081085 1.5480347 2.1577873 1.904541 1.735611 2.3267212 1.3509636 2.739563 1.0882759 3.133606 0.7974434 3.5276184 0.54413605 3.9873657 0.38464737 4.4564514 0.25330353 4.8880005 0.15010834 5.310211 0.06567001 5.7417603 0.009380341 6.1170654 0.0 6.5204773 0.0 6.9145203 0.056289673 7.3367004 0.15010834 7.758911 0.28144836 8.162323 0.44093704 8.546997 0.64733887 8.931641 1.0601273 9.325684 1.5010719 9.700958 1.942009 10.038727 2.3735695 10.329559 2.486145 10.357697 3.311737 10.817413 3.884018 11.051971 4.4563026 11.183319 5.1317863 11.248993 5.844795 11.248993 6.5390396 11.17395 7.233284 11.033203 7.9369125 10.85495 8.612392 10.686066 9.316021 10.479675 9.935215 10.2638855 10.460587 9.963654 10.957817 9.616516 11.464428 9.241241 11.886604 8.82843 12.149288 8.434387 12.327549 7.9653015 12.4401245 7.44928 12.524563 6.8769836 12.5808525 6.248413 12.599613 5.554138 12.599613 4.7754517 12.505798 4.0342407 12.290016 3.3681335 11.942894 2.777069 11.577011 2.2329102 11.211124 1.716919 10.770184 1.3041077 10.282337 0.9663696 9.775726 0.7224121 9.212822 0.4784851 8.678066 0.2626953 8.171459 0.13134766 7.6648407 0.046936035 7.1488533 0.0 6.614094 0.0 6.163769 0.05630493 5.769741 0.093811035 5.4132347 0.17828369 5.159931 0.32836914 4.8597145 0.4315796 4.606411 0.5253906 4.362484 0.6286011 4.1467056 0.7036438 3.9778366 0.7224121 3.855877 0.7411804 3.7995872 0.79748535 3.790203 0.83499146 3.884018 0.9100647 4.1185646 0.9475708 4.1185646 0.9475708 "/>
  </g>
</svg>

The svg, g and path elements are SVG contents. The ss:strokes and ss:stroke elements that have ss namespace are ss one native data.

Though this data just one stroke, there is one ss:stroke element. In most usually cases, there are multiple ss:stroke elements.

Anyway see the ss:stroke element has :

and ss:stroke content text line this:

This values mean x0,y0,x1,y1,...., simply are listed up multiple points of this circle stroke.

Parse this SVG and get native ss one data out

Here I use kotlinc (kotlin script) to get it.

main.kts


import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes

import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory

import java.io.InputStream
import java.io.File


class MyHandler() : DefaultHandler() {
    //override fun startDocument(){ println("start document") }
    //override fun endDocument(){ println("end document") }

    override fun startElement(uri: String, localName: String, qName: String, attrs: Attributes) {
        println("start ${qName}")
    }

    override fun endElement(uri: String, localName: String, qName: String){
        println("end ${qName}")
    }

    override fun characters(ch: CharArray, start: Int, length: Int) {
        val xyParams = 0.until(length).map { index->
            "${ch[start + index]}"
        }.joinToString("")

        println("${xyParams}")
    }
}

val file = File("just-a-circle.svg")

val factory = SAXParserFactory.newInstance()
val parser = factory.newSAXParser()
val handler = MyHandler()

file.inputStream().use { inputStream->
    parser.parse(inputStream, handler)
}

Check the kotlinc version:

$ kotlinc -version
info: kotlinc-jvm 1.8.10 (JRE 17.0.7+7-Ubuntu-0ubuntu122.04.2)

Run it:

$ kotlinc -script main.kts
start svg
start ss:strokes
start ss:stroke
5.0661163,0.9569702,5.047348,0.9475708,4.878479,0.881897,4.6627007,0.83499146,4.4938316,0.81622314,4.3249626,0.81622314,4.09042,0.81622314,3.7995872,0.8725281,3.452465,1.022644,3.0490494,1.229065,2.6081085,1.5480347,2.1577873,1.904541,1.735611,2.3267212,1.3509636,2.739563,1.0882759,3.133606,0.7974434,3.5276184,0.54413605,3.9873657,0.38464737,4.4564514,0.25330353,4.8880005,0.15010834,5.310211,0.06567001,5.7417603,0.009380341,6.1170654,0.0,6.5204773,0.0,6.9145203,0.056289673,7.3367004,0.15010834,7.758911,0.28144836,8.162323,0.44093704,8.546997,0.64733887,8.931641,1.0601273,9.325684,1.5010719,9.700958,1.942009,10.038727,2.3735695,10.329559,2.486145,10.357697,3.311737,10.817413,3.884018,11.051971,4.4563026,11.183319,5.1317863,11.248993,5.844795,11.248993,6.5390396,11.17395,7.233284,11.033203,7.9369125,10.85495,8.612392,10.686066,9.316021,10.479675,9.935215,10.2638855,10.460587,9.963654,10.957817,9.616516,11.464428,9.241241,11.886604,8.82843,12.149288,8.434387,12.327549,7.9653015,12.4401245,7.44928,12.524563,6.8769836,12.5808525,6.248413,12.599613,5.554138,12.599613,4.7754517,12.505798,4.0342407,12.290016,3.3681335,11.942894,2.777069,11.577011,2.2329102,11.211124,1.716919,10.770184,1.3041077,10.282337,0.9663696,9.775726,0.7224121,9.212822,0.4784851,8.678066,0.2626953,8.171459,0.13134766,7.6648407,0.046936035,7.1488533,0.0,6.614094,0.0,6.163769,0.05630493,5.769741,0.093811035,5.4132347,0.17828369,5.159931,0.32836914,4.8597145,0.4315796,4.606411,0.5253906,4.362484,0.6286011,4.1467056,0.7036438,3.9778366,0.7224121,3.855877,0.7411804,3.7995872,0.79748535,3.790203,0.83499146,3.884018,0.9100647,4.1185646,0.9475708,4.1185646,0.9475708
end ss:stroke
end ss:strokes
start g
start path
end path
end g
end svg

If you only get points data, change the main.kts.

main2.kts

import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes

import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory

import java.io.InputStream
import java.io.File

class MyHandler(private val callbackStrokeData: (String)->Unit) : DefaultHandler() {
    private var underStrokeElement = false

    override fun startElement(uri: String, localName: String, qName: String, attrs: Attributes) {
        if( qName=="ss:stroke" ){ underStrokeElement = true }
    }

    override fun endElement(uri: String, localName: String, qName: String){
        if( qName=="ss:stroke" ){ underStrokeElement = false }
    }

    override fun characters(ch: CharArray, start: Int, length: Int) {
        if( underStrokeElement ){
            val xyParams = 0.until(length).map { index->
                "${ch[start + index]}"
            }.joinToString("")
    
            callbackStrokeData(xyParams)
        }
    }
}

val file = File("just-a-circle.svg")

val factory = SAXParserFactory.newInstance()
val parser = factory.newSAXParser()

val callbackF: (String)->Unit = { xyParams->
    val xyList = xyParams.split(",").map { it.toFloat() }
    //val xList  = xyList.mapIndexed { index, value-> if(index%2==0){ value } else { null } }.filter { it!=null }
    //val yList  = xyList.mapIndexed { index, value-> if(index%2==1){ value } else { null } }.filter { it!=null }
    val xList  = xyList.mapIndexed { index, value-> if(index%2==0){ value } else { null } }.mapNotNull{ it }
    val yList  = xyList.mapIndexed { index, value-> if(index%2==1){ value } else { null } }.mapNotNull{ it }

    val ptList = xList.zip(yList).map {
    	"(${it.first}, ${it.second})"
    }.joinToString(", ")

    println("${ptList}")
}

val handler = MyHandler(callbackF)
file.inputStream().use { inputStream->
    parser.parse(inputStream, handler)
}

I changed this code MyHandler accept the callbackStrokeData function. Only callback the stroke content when it comes with ss:stroke one. And the callbackF parse callbacked xyParams data and change it with Point(x,y) list data.

Let's Run it:

$ kotlinc -script main2.kts
(5.0661163, 0.9569702), (5.047348, 0.9475708), (4.878479, 0.881897), (4.6627007, 0.83499146), (4.4938316, 0.81622314), (4.3249626, 0.81622314), (4.09042, 0.81622314), (3.7995872, 0.8725281), (3.452465, 1.022644), (3.0490494, 1.229065), (2.6081085, 1.5480347), (2.1577873, 1.904541), (1.735611, 2.3267212), (1.3509636, 2.739563), (1.0882759, 3.133606), (0.7974434, 3.5276184), (0.54413605, 3.9873657), (0.38464737, 4.4564514), (0.25330353, 4.8880005), (0.15010834, 5.310211), (0.06567001, 5.7417603), (0.009380341, 6.1170654), (0.0, 6.5204773), (0.0, 6.9145203), (0.056289673, 7.3367004), (0.15010834, 7.758911), (0.28144836, 8.162323), (0.44093704, 8.546997), (0.64733887, 8.931641), (1.0601273, 9.325684), (1.5010719, 9.700958), (1.942009, 10.038727), (2.3735695, 10.329559), (2.486145, 10.357697), (3.311737, 10.817413), (3.884018, 11.051971), (4.4563026, 11.183319), (5.1317863, 11.248993), (5.844795, 11.248993), (6.5390396, 11.17395), (7.233284, 11.033203), (7.9369125, 10.85495), (8.612392, 10.686066), (9.316021, 10.479675), (9.935215, 10.2638855), (10.460587, 9.963654), (10.957817, 9.616516), (11.464428, 9.241241), (11.886604, 8.82843), (12.149288, 8.434387), (12.327549, 7.9653015), (12.4401245, 7.44928), (12.524563, 6.8769836), (12.5808525, 6.248413), (12.599613, 5.554138), (12.599613, 4.7754517), (12.505798, 4.0342407), (12.290016, 3.3681335), (11.942894, 2.777069), (11.577011, 2.2329102), (11.211124, 1.716919), (10.770184, 1.3041077), (10.282337, 0.9663696), (9.775726, 0.7224121), (9.212822, 0.4784851), (8.678066, 0.2626953), (8.171459, 0.13134766), (7.6648407, 0.046936035), (7.1488533, 0.0), (6.614094, 0.0), (6.163769, 0.05630493), (5.769741, 0.093811035), (5.4132347, 0.17828369), (5.159931, 0.32836914), (4.8597145, 0.4315796), (4.606411, 0.5253906), (4.362484, 0.6286011), (4.1467056, 0.7036438), (3.9778366, 0.7224121), (3.855877, 0.7411804), (3.7995872, 0.79748535), (3.790203, 0.83499146), (3.884018, 0.9100647), (4.1185646, 0.9475708), (4.1185646, 0.9475708)

You get this stroke as Point List by this code.

Get Attributes

I forgot gettting attributes.

Let's change the MyHandler class in main.kts like this.

class MyHandler() : DefaultHandler() {
    override fun startElement(uri: String, localName: String, qName: String, attrs: Attributes) {
        println("start ${qName}")
        0.until(attrs.length).forEach {
            val name = attrs.getLocalName(it)
            val value = attrs.getValue(it)
            println("- attr ${name} ${value}")
        }
    }

    override fun endElement(uri: String, localName: String, qName: String){
        println("end ${qName}")
    }
}

Run it:

$ kotlinc -script main.kts
start svg
- attr version 1.1
- attr xmlns http://www.w3.org/2000/svg
- attr xmlns:ss http://ns.smallsketch.app/SmallSketch/1.0
- attr x 0.0mm
- attr y 0.0mm
- attr width 12.599613mm
- attr height 11.248993mm
- attr viewBox 0.0 0.0 12.599613 11.248993
- attr xmlns:xlink http://www.w3.org/1999/xlink
- attr preserveAspectRatio xMidYMid meet
- attr zoomAndPan magnify
- attr contentScriptType application/ecmascript
- attr contentStyleType text/css
start ss:strokes
start ss:stroke
- attr color rgb(76, 96, 130)
- attr strokewidth 0.254
end ss:stroke
end ss:strokes
start g
- attr stroke rgb(76, 96, 130)
- attr stroke-width 0.254
- attr fill none
start path
- attr d M 5.0661163 0.9569702 L 5.047348 0.9475708 4.878479 0.881897 4.6627007 0.83499146 4.4938316 0.81622314 4.3249626 0.81622314 4.09042 0.81622314 3.7995872 0.8725281 3.452465 1.022644 3.0490494 1.229065 2.6081085 1.5480347 2.1577873 1.904541 1.735611 2.3267212 1.3509636 2.739563 1.0882759 3.133606 0.7974434 3.5276184 0.54413605 3.9873657 0.38464737 4.4564514 0.25330353 4.8880005 0.15010834 5.310211 0.06567001 5.7417603 0.009380341 6.1170654 0.0 6.5204773 0.0 6.9145203 0.056289673 7.3367004 0.15010834 7.758911 0.28144836 8.162323 0.44093704 8.546997 0.64733887 8.931641 1.0601273 9.325684 1.5010719 9.700958 1.942009 10.038727 2.3735695 10.329559 2.486145 10.357697 3.311737 10.817413 3.884018 11.051971 4.4563026 11.183319 5.1317863 11.248993 5.844795 11.248993 6.5390396 11.17395 7.233284 11.033203 7.9369125 10.85495 8.612392 10.686066 9.316021 10.479675 9.935215 10.2638855 10.460587 9.963654 10.957817 9.616516 11.464428 9.241241 11.886604 8.82843 12.149288 8.434387 12.327549 7.9653015 12.4401245 7.44928 12.524563 6.8769836 12.5808525 6.248413 12.599613 5.554138 12.599613 4.7754517 12.505798 4.0342407 12.290016 3.3681335 11.942894 2.777069 11.577011 2.2329102 11.211124 1.716919 10.770184 1.3041077 10.282337 0.9663696 9.775726 0.7224121 9.212822 0.4784851 8.678066 0.2626953 8.171459 0.13134766 7.6648407 0.046936035 7.1488533 0.0 6.614094 0.0 6.163769 0.05630493 5.769741 0.093811035 5.4132347 0.17828369 5.159931 0.32836914 4.8597145 0.4315796 4.606411 0.5253906 4.362484 0.6286011 4.1467056 0.7036438 3.9778366 0.7224121 3.855877 0.7411804 3.7995872 0.79748535 3.790203 0.83499146 3.884018 0.9100647 4.1185646 0.9475708 4.1185646 0.9475708 
end path
end g
end svg

This way, you can get the stroke color and stroke width data.

Wrap up, show main3.kts:

import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes

import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory

import java.io.InputStream
import java.io.File


class MyHandler() : DefaultHandler() {
    override fun startElement(uri: String, localName: String, qName: String, attrs: Attributes) {
        println("start ${qName}")
        0.until(attrs.length).forEach {
            val name = attrs.getLocalName(it)
            val value = attrs.getValue(it)
            println("- attr ${name} ${value}")
        }
    }

    override fun endElement(uri: String, localName: String, qName: String){
        println("end ${qName}")
    }
}

val file = File("just-a-circle.svg")

val factory = SAXParserFactory.newInstance()
val parser = factory.newSAXParser()
val handler = MyHandler()

file.inputStream().use { inputStream->
    parser.parse(inputStream, handler)
}

That's all.