Task 800: .VUE File Format

Task 800: .VUE File Format

The .VUE file format refers to the Vue.js Single-File Component (SFC) format, which encapsulates a Vue component's template, logic, and styling in a single text-based file with an HTML-like syntax. The format is documented in the official Vue.js SFC specification.

  1. The properties intrinsic to the .VUE file format are as follows, based on its structural elements and attributes:
  • Filename: Used for automatic component name inference (e.g., for DevTools, self-references, and warnings).
  • Template block: A single optional top-level <template> block containing the component's HTML structure.
  • Template language: Specified via the 'lang' attribute on the <template> block (e.g., 'pug' for Pug templating).
  • Template source: Specified via the 'src' attribute on the <template> block for external file imports.
  • Script block: A single optional top-level <script> block containing the component's JavaScript logic (executed as an ES module).
  • Script setup: A boolean indicator for whether the script block uses the 'setup' attribute (i.e., <script setup>), enabling composition API features.
  • Script language: Specified via the 'lang' attribute on the <script> block (e.g., 'ts' for TypeScript).
  • Script source: Specified via the 'src' attribute on the <script> block for external file imports.
  • Style blocks: Zero or more <style> blocks containing CSS for the component (multiple allowed with different encapsulation modes).
  • Style scoped: A boolean attribute on a <style> block to scope styles to the component.
  • Style module: A boolean attribute on a <style> block to enable CSS modules.
  • Style language: Specified via the 'lang' attribute on a <style> block (e.g., 'scss' for SCSS).
  • Style source: Specified via the 'src' attribute on a <style> block for external file imports.
  • Custom blocks: Zero or more custom top-level blocks (e.g., <docs>) for project-specific content.
  • Custom block tag: The tag name of each custom block.
  • Custom block language: Specified via the 'lang' attribute on a custom block.
  • Custom block source: Specified via the 'src' attribute on a custom block for external file imports.
  • Comments: Top-level comments using HTML syntax (), or language-specific comments within blocks.
  1. Two direct download links for .VUE files:
  1. The following is an HTML document with embedded JavaScript that enables drag-and-drop functionality for a .VUE file. Upon dropping a file, it parses the content and displays all properties listed in item 1 on the screen.
.VUE File Properties Dumper
Drag and drop a .VUE file here

The following is a Python class that can open, parse (decode), read, write, and print to console all properties from the list in item 1.

import xml.etree.ElementTree as ET
import os

class VueFileHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {
            'filename': os.path.basename(filepath).replace('.vue', ''),
            'templateBlock': None,
            'templateLang': None,
            'templateSrc': None,
            'scriptBlock': None,
            'scriptSetup': False,
            'scriptLang': None,
            'scriptSrc': None,
            'styleBlocks': [],
            'customBlocks': [],
            'comments': []
        }
        self.parse()

    def parse(self):
        with open(self.filepath, 'r', encoding='utf-8') as f:
            content = f.read()

        # Extract comments
        import re
        comment_regex = r'<!--([\s\S]*?)-->'
        self.properties['comments'] = re.findall(comment_regex, content)

        # Parse as XML (wrap in root for validity)
        try:
            root = ET.fromstring(f'<root>{content}</root>')
            template = root.find('template')
            if template is not None:
                self.properties['templateBlock'] = ET.tostring(template, encoding='unicode', method='html').replace('<template>', '').replace('</template>', '').strip()
                self.properties['templateLang'] = template.get('lang')
                self.properties['templateSrc'] = template.get('src')

            script = root.find('script')
            if script is not None:
                self.properties['scriptBlock'] = ET.tostring(script, encoding='unicode', method='html').replace('<script>', '').replace('</script>', '').strip()
                self.properties['scriptSetup'] = 'setup' in script.attrib
                self.properties['scriptLang'] = script.get('lang')
                self.properties['scriptSrc'] = script.get('src')

            for style in root.findall('style'):
                self.properties['styleBlocks'].append({
                    'content': ET.tostring(style, encoding='unicode', method='html').replace('<style>', '').replace('</style>', '').strip(),
                    'scoped': 'scoped' in style.attrib,
                    'module': 'module' in style.attrib,
                    'lang': style.get('lang'),
                    'src': style.get('src')
                })

            for child in root:
                tag = child.tag.lower()
                if tag not in ['template', 'script', 'style']:
                    self.properties['customBlocks'].append({
                        'tag': tag,
                        'content': ET.tostring(child, encoding='unicode', method='html').strip(),
                        'lang': child.get('lang'),
                        'src': child.get('src')
                    })
        except ET.ParseError as e:
            print(f"Parse error: {e}")

    def print_properties(self):
        import json
        print(json.dumps(self.properties, indent=4, ensure_ascii=False))

    def write(self, output_path=None):
        if output_path is None:
            output_path = self.filepath
        content = ''
        if self.properties['comments']:
            content += '\n'.join([f'<!--{c}-->' for c in self.properties['comments']]) + '\n'

        if self.properties['templateBlock']:
            attrs = ''
            if self.properties['templateLang']: attrs += f' lang="{self.properties["templateLang"]}"'
            if self.properties['templateSrc']: attrs += f' src="{self.properties["templateSrc"]}"'
            content += f'<template{attrs}>{self.properties["templateBlock"]}</template>\n'

        if self.properties['scriptBlock']:
            attrs = ''
            if self.properties['scriptSetup']: attrs += ' setup'
            if self.properties['scriptLang']: attrs += f' lang="{self.properties["scriptLang"]}"'
            if self.properties['scriptSrc']: attrs += f' src="{self.properties["scriptSrc"]}"'
            content += f'<script{attrs}>{self.properties["scriptBlock"]}</script>\n'

        for style in self.properties['styleBlocks']:
            attrs = ''
            if style['scoped']: attrs += ' scoped'
            if style['module']: attrs += ' module'
            if style['lang']: attrs += f' lang="{style["lang"]}"'
            if style['src']: attrs += f' src="{style["src"]}"'
            content += f'<style{attrs}>{style["content"]}</style>\n'

        for custom in self.properties['customBlocks']:
            attrs = ''
            if custom['lang']: attrs += f' lang="{custom["lang"]}"'
            if custom['src']: attrs += f' src="{custom["src"]}"'
            content += f'<{custom["tag"]}{attrs}>{custom["content"]}</{custom["tag"]}>\n'

        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(content)

# Example usage:
# handler = VueFileHandler('path/to/file.vue')
# handler.print_properties()
# handler.write('path/to/output.vue')

The following is a Java class that can open, parse (decode), read, write, and print to console all properties from the list in item 1.

import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;

public class VueFileHandler {
    private String filepath;
    private Map<String, Object> properties;

    public VueFileHandler(String filepath) {
        this.filepath = filepath;
        this.properties = new HashMap<>();
        properties.put("filename", new File(filepath).getName().replace(".vue", ""));
        properties.put("templateBlock", null);
        properties.put("templateLang", null);
        properties.put("templateSrc", null);
        properties.put("scriptBlock", null);
        properties.put("scriptSetup", false);
        properties.put("scriptLang", null);
        properties.put("scriptSrc", null);
        properties.put("styleBlocks", new ArrayList<Map<String, Object>>());
        properties.put("customBlocks", new ArrayList<Map<String, Object>>());
        properties.put("comments", new ArrayList<String>());
        parse();
    }

    private void parse() {
        try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
            StringBuilder content = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                content.append(line).append("\n");
            }
            String fileContent = content.toString();

            // Extract comments
            Pattern commentPattern = Pattern.compile("<!--([\\s\\S]*?)-->");
            Matcher matcher = commentPattern.matcher(fileContent);
            List<String> comments = (List<String>) properties.get("comments");
            while (matcher.find()) {
                comments.add(matcher.group(1).trim());
            }

            // Parse as XML
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            InputStream is = new ByteArrayInputStream(("<root>" + fileContent + "</root>").getBytes("UTF-8"));
            Document doc = builder.parse(is);

            Node template = doc.getElementsByTagName("template").item(0);
            if (template != null) {
                properties.put("templateBlock", getInnerContent(template));
                NamedNodeMap attrs = template.getAttributes();
                properties.put("templateLang", attrs.getNamedItem("lang") != null ? attrs.getNamedItem("lang").getNodeValue() : null);
                properties.put("templateSrc", attrs.getNamedItem("src") != null ? attrs.getNamedItem("src").getNodeValue() : null);
            }

            Node script = doc.getElementsByTagName("script").item(0);
            if (script != null) {
                properties.put("scriptBlock", getInnerContent(script));
                NamedNodeMap attrs = script.getAttributes();
                properties.put("scriptSetup", attrs.getNamedItem("setup") != null);
                properties.put("scriptLang", attrs.getNamedItem("lang") != null ? attrs.getNamedItem("lang").getNodeValue() : null);
                properties.put("scriptSrc", attrs.getNamedItem("src") != null ? attrs.getNamedItem("src").getNodeValue() : null);
            }

            NodeList styles = doc.getElementsByTagName("style");
            List<Map<String, Object>> styleBlocks = (List<Map<String, Object>>) properties.get("styleBlocks");
            for (int i = 0; i < styles.getLength(); i++) {
                Node style = styles.item(i);
                NamedNodeMap attrs = style.getAttributes();
                Map<String, Object> styleMap = new HashMap<>();
                styleMap.put("content", getInnerContent(style));
                styleMap.put("scoped", attrs.getNamedItem("scoped") != null);
                styleMap.put("module", attrs.getNamedItem("module") != null);
                styleMap.put("lang", attrs.getNamedItem("lang") != null ? attrs.getNamedItem("lang").getNodeValue() : null);
                styleMap.put("src", attrs.getNamedItem("src") != null ? attrs.getNamedItem("src").getNodeValue() : null);
                styleBlocks.add(styleMap);
            }

            NodeList children = doc.getDocumentElement().getChildNodes();
            List<Map<String, Object>> customBlocks = (List<Map<String, Object>>) properties.get("customBlocks");
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String tag = child.getNodeName().toLowerCase();
                    if (!tag.equals("template") && !tag.equals("script") && !tag.equals("style")) {
                        NamedNodeMap attrs = child.getAttributes();
                        Map<String, Object> customMap = new HashMap<>();
                        customMap.put("tag", tag);
                        customMap.put("content", getInnerContent(child));
                        customMap.put("lang", attrs.getNamedItem("lang") != null ? attrs.getNamedItem("lang").getNodeValue() : null);
                        customMap.put("src", attrs.getNamedItem("src") != null ? attrs.getNamedItem("src").getNodeValue() : null);
                        customBlocks.add(customMap);
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("Parse error: " + e.getMessage());
        }
    }

    private String getInnerContent(Node node) {
        StringBuilder sb = new StringBuilder();
        NodeList children = node.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
                sb.append(child.getNodeValue());
            } else if (child.getNodeType() == Node.ELEMENT_NODE) {
                sb.append(child.getTextContent());
            }
        }
        return sb.toString().trim();
    }

    public void printProperties() {
        System.out.println(new com.google.gson.GsonBuilder().setPrettyPrinting().create().toJson(properties));
    }

    public void write(String outputPath) throws IOException {
        if (outputPath == null) outputPath = filepath;
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath))) {
            List<String> comments = (List<String>) properties.get("comments");
            for (String comment : comments) {
                bw.write("<!--" + comment + "-->\n");
            }

            if (properties.get("templateBlock") != null) {
                String attrs = "";
                if (properties.get("templateLang") != null) attrs += " lang=\"" + properties.get("templateLang") + "\"";
                if (properties.get("templateSrc") != null) attrs += " src=\"" + properties.get("templateSrc") + "\"";
                bw.write("<template" + attrs + ">" + properties.get("templateBlock") + "</template>\n");
            }

            if (properties.get("scriptBlock") != null) {
                String attrs = "";
                if ((Boolean) properties.get("scriptSetup")) attrs += " setup";
                if (properties.get("scriptLang") != null) attrs += " lang=\"" + properties.get("scriptLang") + "\"";
                if (properties.get("scriptSrc") != null) attrs += " src=\"" + properties.get("scriptSrc") + "\"";
                bw.write("<script" + attrs + ">" + properties.get("scriptBlock") + "</script>\n");
            }

            List<Map<String, Object>> styleBlocks = (List<Map<String, Object>>) properties.get("styleBlocks");
            for (Map<String, Object> style : styleBlocks) {
                String attrs = "";
                if ((Boolean) style.get("scoped")) attrs += " scoped";
                if ((Boolean) style.get("module")) attrs += " module";
                if (style.get("lang") != null) attrs += " lang=\"" + style.get("lang") + "\"";
                if (style.get("src") != null) attrs += " src=\"" + style.get("src") + "\"";
                bw.write("<style" + attrs + ">" + style.get("content") + "</style>\n");
            }

            List<Map<String, Object>> customBlocks = (List<Map<String, Object>>) properties.get("customBlocks");
            for (Map<String, Object> custom : customBlocks) {
                String attrs = "";
                if (custom.get("lang") != null) attrs += " lang=\"" + custom.get("lang") + "\"";
                if (custom.get("src") != null) attrs += " src=\"" + custom.get("src") + "\"";
                bw.write("<" + custom.get("tag") + attrs + ">" + custom.get("content") + "</" + custom.get("tag") + ">\n");
            }
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     VueFileHandler handler = new VueFileHandler("path/to/file.vue");
    //     handler.printProperties();
    //     handler.write("path/to/output.vue");
    // }
}
  1. The following is a JavaScript class that can open, parse (decode), read, write, and print to console all properties from the list in item 1. (Note: File operations require Node.js environment.)
const fs = require('fs');
const { DOMParser } = require('xmldom');

class VueFileHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {
            filename: filepath.split('/').pop().replace('.vue', ''),
            templateBlock: null,
            templateLang: null,
            templateSrc: null,
            scriptBlock: null,
            scriptSetup: false,
            scriptLang: null,
            scriptSrc: null,
            styleBlocks: [],
            customBlocks: [],
            comments: []
        };
        this.parse();
    }

    parse() {
        const content = fs.readFileSync(this.filepath, 'utf-8');

        // Extract comments
        const commentRegex = /<!--([\s\S]*?)-->/g;
        let match;
        while ((match = commentRegex.exec(content)) !== null) {
            this.properties.comments.push(match[1].trim());
        }

        // Parse as XML
        const parser = new DOMParser();
        const doc = parser.parseFromString(`<root>${content}</root>`, 'text/xml');

        const template = doc.getElementsByTagName('template')[0];
        if (template) {
            this.properties.templateBlock = template.textContent.trim();
            this.properties.templateLang = template.getAttribute('lang');
            this.properties.templateSrc = template.getAttribute('src');
        }

        const script = doc.getElementsByTagName('script')[0];
        if (script) {
            this.properties.scriptBlock = script.textContent.trim();
            this.properties.scriptSetup = script.hasAttribute('setup');
            this.properties.scriptLang = script.getAttribute('lang');
            this.properties.scriptSrc = script.getAttribute('src');
        }

        const styles = doc.getElementsByTagName('style');
        for (let i = 0; i < styles.length; i++) {
            const style = styles[i];
            this.properties.styleBlocks.push({
                content: style.textContent.trim(),
                scoped: style.hasAttribute('scoped'),
                module: style.hasAttribute('module'),
                lang: style.getAttribute('lang'),
                src: style.getAttribute('src')
            });
        }

        const children = doc.documentElement.childNodes;
        for (let i = 0; i < children.length; i++) {
            const child = children[i];
            if (child.nodeType === 1) { // Element node
                const tag = child.tagName.toLowerCase();
                if (!['template', 'script', 'style'].includes(tag)) {
                    this.properties.customBlocks.push({
                        tag: tag,
                        content: child.textContent.trim(),
                        lang: child.getAttribute('lang'),
                        src: child.getAttribute('src')
                    });
                }
            }
        }
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 2));
    }

    write(outputPath = this.filepath) {
        let content = '';
        this.properties.comments.forEach(c => {
            content += `<!--${c}-->\n`;
        });

        if (this.properties.templateBlock) {
            let attrs = '';
            if (this.properties.templateLang) attrs += ` lang="${this.properties.templateLang}"`;
            if (this.properties.templateSrc) attrs += ` src="${this.properties.templateSrc}"`;
            content += `<template${attrs}>${this.properties.templateBlock}</template>\n`;
        }

        if (this.properties.scriptBlock) {
            let attrs = '';
            if (this.properties.scriptSetup) attrs += ' setup';
            if (this.properties.scriptLang) attrs += ` lang="${this.properties.scriptLang}"`;
            if (this.properties.scriptSrc) attrs += ` src="${this.properties.scriptSrc}"`;
            content += `<script${attrs}>${this.properties.scriptBlock}</script>\n`;
        }

        this.properties.styleBlocks.forEach(style => {
            let attrs = '';
            if (style.scoped) attrs += ' scoped';
            if (style.module) attrs += ' module';
            if (style.lang) attrs += ` lang="${style.lang}"`;
            if (style.src) attrs += ` src="${style.src}"`;
            content += `<style${attrs}>${style.content}</style>\n`;
        });

        this.properties.customBlocks.forEach(custom => {
            let attrs = '';
            if (custom.lang) attrs += ` lang="${custom.lang}"`;
            if (custom.src) attrs += ` src="${custom.src}"`;
            content += `<${custom.tag}${attrs}>${custom.content}</${custom.tag}>\n`;
        });

        fs.writeFileSync(outputPath, content, 'utf-8');
    }
}

// Example usage:
// const handler = new VueFileHandler('path/to/file.vue');
// handler.printProperties();
// handler.write('path/to/output.vue');
  1. The following is a C++ class that can open, parse (decode), read, write, and print to console all properties from the list in item 1. (Note: Parsing is simplified using regex and string manipulation due to lack of built-in XML support; for production, use a library like TinyXML.)
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <regex>
#include <map>

struct StyleBlock {
    std::string content;
    bool scoped;
    bool module;
    std::string lang;
    std::string src;
};

struct CustomBlock {
    std::string tag;
    std::string content;
    std::string lang;
    std::string src;
};

class VueFileHandler {
private:
    std::string filepath;
    std::string filename;
    std::string templateBlock;
    std::string templateLang;
    std::string templateSrc;
    std::string scriptBlock;
    bool scriptSetup;
    std::string scriptLang;
    std::string scriptSrc;
    std::vector<StyleBlock> styleBlocks;
    std::vector<CustomBlock> customBlocks;
    std::vector<std::string> comments;

    void extractAttribute(const std::string& block, const std::string& attr, std::string& value) {
        std::regex attrRegex(attr + R"(="([^"]*)")");
        std::smatch match;
        if (std::regex_search(block, match, attrRegex)) {
            value = match[1];
        }
    }

    bool hasAttribute(const std::string& block, const std::string& attr) {
        return block.find(attr + " ") != std::string::npos || block.find(attr + ">") != std::string::npos || block.find(attr + "=\"") != std::string::npos;
    }

public:
    VueFileHandler(const std::string& fp) : filepath(fp), scriptSetup(false) {
        size_t pos = filepath.find_last_of('/');
        filename = (pos == std::string::npos ? filepath : filepath.substr(pos + 1)).substr(0, filepath.rfind(".vue"));
        parse();
    }

    void parse() {
        std::ifstream file(filepath);
        if (!file.is_open()) {
            std::cerr << "Failed to open file." << std::endl;
            return;
        }
        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();

        // Extract comments
        std::regex commentRegex(R"(<!--([\s\S]*?)-->)");
        std::sregex_iterator iter(content.begin(), content.end(), commentRegex);
        std::sregex_iterator end;
        for (; iter != end; ++iter) {
            comments.push_back(iter->str(1));
        }

        // Simplified block extraction using regex (not full parser)
        std::regex blockRegex(R"(<(\w+)([^>]*)>([\s\S]*?)</\1>)");
        std::sregex_iterator blockIter(content.begin(), content.end(), blockRegex);
        for (; blockIter != end; ++blockIter) {
            std::string tag = blockIter->str(1);
            std::string attrs = blockIter->str(2);
            std::string inner = blockIter->str(3);

            if (tag == "template") {
                templateBlock = inner;
                extractAttribute(attrs, "lang", templateLang);
                extractAttribute(attrs, "src", templateSrc);
            } else if (tag == "script") {
                scriptBlock = inner;
                scriptSetup = hasAttribute(attrs, "setup");
                extractAttribute(attrs, "lang", scriptLang);
                extractAttribute(attrs, "src", scriptSrc);
            } else if (tag == "style") {
                StyleBlock sb;
                sb.content = inner;
                sb.scoped = hasAttribute(attrs, "scoped");
                sb.module = hasAttribute(attrs, "module");
                extractAttribute(attrs, "lang", sb.lang);
                extractAttribute(attrs, "src", sb.src);
                styleBlocks.push_back(sb);
            } else {
                CustomBlock cb;
                cb.tag = tag;
                cb.content = inner;
                extractAttribute(attrs, "lang", cb.lang);
                extractAttribute(attrs, "src", cb.src);
                customBlocks.push_back(cb);
            }
        }
    }

    void printProperties() {
        std::cout << "{\n";
        std::cout << "  \"filename\": \"" << filename << "\",\n";
        std::cout << "  \"templateBlock\": " << (templateBlock.empty() ? "null" : "\"" + templateBlock + "\"") << ",\n";
        std::cout << "  \"templateLang\": " << (templateLang.empty() ? "null" : "\"" + templateLang + "\"") << ",\n";
        std::cout << "  \"templateSrc\": " << (templateSrc.empty() ? "null" : "\"" + templateSrc + "\"") << ",\n";
        std::cout << "  \"scriptBlock\": " << (scriptBlock.empty() ? "null" : "\"" + scriptBlock + "\"") << ",\n";
        std::cout << "  \"scriptSetup\": " << (scriptSetup ? "true" : "false") << ",\n";
        std::cout << "  \"scriptLang\": " << (scriptLang.empty() ? "null" : "\"" + scriptLang + "\"") << ",\n";
        std::cout << "  \"scriptSrc\": " << (scriptSrc.empty() ? "null" : "\"" + scriptSrc + "\"") << ",\n";
        std::cout << "  \"styleBlocks\": [\n";
        for (size_t i = 0; i < styleBlocks.size(); ++i) {
            auto& sb = styleBlocks[i];
            std::cout << "    {\n";
            std::cout << "      \"content\": \"" << sb.content << "\",\n";
            std::cout << "      \"scoped\": " << (sb.scoped ? "true" : "false") << ",\n";
            std::cout << "      \"module\": " << (sb.module ? "true" : "false") << ",\n";
            std::cout << "      \"lang\": " << (sb.lang.empty() ? "null" : "\"" + sb.lang + "\"") << ",\n";
            std::cout << "      \"src\": " << (sb.src.empty() ? "null" : "\"" + sb.src + "\"") << "\n";
            std::cout << "    }" << (i < styleBlocks.size() - 1 ? "," : "") << "\n";
        }
        std::cout << "  ],\n";
        std::cout << "  \"customBlocks\": [\n";
        for (size_t i = 0; i < customBlocks.size(); ++i) {
            auto& cb = customBlocks[i];
            std::cout << "    {\n";
            std::cout << "      \"tag\": \"" << cb.tag << "\",\n";
            std::cout << "      \"content\": \"" << cb.content << "\",\n";
            std::cout << "      \"lang\": " << (cb.lang.empty() ? "null" : "\"" + cb.lang + "\"") << ",\n";
            std::cout << "      \"src\": " << (cb.src.empty() ? "null" : "\"" + cb.src + "\"") << "\n";
            std::cout << "    }" << (i < customBlocks.size() - 1 ? "," : "") << "\n";
        }
        std::cout << "  ],\n";
        std::cout << "  \"comments\": [\n";
        for (size_t i = 0; i < comments.size(); ++i) {
            std::cout << "    \"" << comments[i] << "\"" << (i < comments.size() - 1 ? "," : "") << "\n";
        }
        std::cout << "  ]\n";
        std::cout << "}\n";
    }

    void write(const std::string& outputPath) {
        std::ofstream file(outputPath.empty() ? filepath : outputPath);
        if (!file.is_open()) {
            std::cerr << "Failed to write file." << std::endl;
            return;
        }
        for (const auto& c : comments) {
            file << "<!--" << c << "-->\n";
        }
        if (!templateBlock.empty()) {
            file << "<template";
            if (!templateLang.empty()) file << " lang=\"" << templateLang << "\"";
            if (!templateSrc.empty()) file << " src=\"" << templateSrc << "\"";
            file << ">" << templateBlock << "</template>\n";
        }
        if (!scriptBlock.empty()) {
            file << "<script";
            if (scriptSetup) file << " setup";
            if (!scriptLang.empty()) file << " lang=\"" << scriptLang << "\"";
            if (!scriptSrc.empty()) file << " src=\"" << scriptSrc << "\"";
            file << ">" << scriptBlock << "</script>\n";
        }
        for (const auto& sb : styleBlocks) {
            file << "<style";
            if (sb.scoped) file << " scoped";
            if (sb.module) file << " module";
            if (!sb.lang.empty()) file << " lang=\"" << sb.lang << "\"";
            if (!sb.src.empty()) file << " src=\"" << sb.src << "\"";
            file << ">" << sb.content << "</style>\n";
        }
        for (const auto& cb : customBlocks) {
            file << "<" << cb.tag;
            if (!cb.lang.empty()) file << " lang=\"" << cb.lang << "\"";
            if (!cb.src.empty()) file << " src=\"" << cb.src << "\"";
            file << ">" << cb.content << "</" << cb.tag << ">\n";
        }
        file.close();
    }
};

// Example usage:
// int main() {
//     VueFileHandler handler("path/to/file.vue");
//     handler.printProperties();
//     handler.write("path/to/output.vue");
//     return 0;
// }