JSON String Extractor

by xowap

HTML

<div id="app">
    <h1>JSON string extractor</h1>
    
    <h2>Input</h2>

    <div class="field">
        <label for="input">Input code</label>
        <textarea v-model="input" id="input" cols="50" rows="10" @input="resizeInput()"></textarea>
    </div>
    
    <h2>Output</h2>

    <div class="output" v-for="(string, index) of output" :key="index">
        <h3>String #{{ index }}</h3>
        <pre>{{ string }}</pre>
    </div>
</div>

SASS

@import url('https://fonts.googleapis.com/css?family=Heebo|Inconsolata|Noto+Serif:700');

$body-padding: 20px

body
    font-family: 'Heebo', sans-serif
    font-size: 16px
    padding: 0 $body-padding $body-padding $body-padding

h1, h2, h3
    font-family: 'Noto Serif', serif
    font-weight: bold
    
h1
    font-size: 28px
    margin: 30px 0
    
h2
    font-size: 24px
    margin: 20px 0
    
h3
    font-size: 20px
    margin: 10px 0
    
textarea, pre
    font-family: 'Inconsolata', monospace
    
pre
    background-color: #ccc
    padding: 15px (15px + $body-padding)
    margin: 0 (- $body-padding)
    
label
    position: absolute
    overflow: hidden
    width: 0
    height: 0
    
textarea
    background-color: #eee
    margin: 0 (- $body-padding)
    padding: 15px (15px + $body-padding)
    height: 0
    border: 0
    box-sizing: border-box
    resize: none
    
    &:hover,
    &:focus
        background-color: #ccc;

.field
    display: flex
    flex-direction: column

JavaScript

function findStrings(string) {
	const re = /"(([^\0-\x19"\\]|\\[^\0-\x19])*)"/g;
    const out = [];

    while ((m = re.exec(string)) !== null) {
	    if (m.index === re.lastIndex) {
        	re.lastIndex++;
    	}
    
    	out.push(m[1]);
    }
    
    return out;
}

function subEscapes(string) {
	const re = /\\(["\\\/bnrt]|u([a-fA-F0-9]{4}))/g;
    const map = {
        '"': '"',
        '\\': '\\',
        '/': '/',
        'b': '\b',
        'n': '\n',
        'r': '\r',
        't': '\t',
    };
    
    return string.replace(re, (_, char, hexCodePoint) => {
    	if (char[0] === 'u') {
        	return String.fromCodePoint(parseInt(hexCodePoint, 16));
        } else {
        	return map[char];
        }
    })
}

const app = new Vue({
	el: '#app',
    data() {
    	return {
        	input: `const foo = "say \\"foo\\" \\ud83e\\udd37";`,
        };
    },
    methods: {
    	resizeInput() {
        	const ta = this.$el.querySelector('textarea');
            ta.style.setProperty('height', '0');
            const height = ta.scrollHeight;
            ta.style.setProperty('height', `${height}px`);
        },
    },
    computed: {
    	output() {
        	return findStrings(this.input).map(subEscapes);
        },
    },
    mounted() {
    	this.resizeInput();
    },
});