NaturalIntelligence / fast-xml-parser

Validate XML, Parse XML and Build XML rapidly without C/C++ based libraries and no callback.
https://naturalintelligence.github.io/fast-xml-parser/
MIT License
2.45k stars 296 forks source link

There is an exception appearing at XMLBuilder.build(obj): textValue.replace is not a function #600

Closed HollisMeynell closed 12 months ago

HollisMeynell commented 1 year ago

Description

I want to modify an SVG in a Node environment. I have successfully serialized it into a JavaScript object, but when trying to convert it back to XML, I encountered errors that I cannot resolve. I am providing the detailed output of the XML file and JSON.Perhaps I have misconfigured something, but I don't know how to set it up correctly, and I'm unsure of the proper configuration needed to preserve the attributes of the XML. As you can see, this is a piece of SVG code, and it may contain attributes with special characters, such as xlink:href.

Input

xml:

<svg xmlns="http://www.w3.org/2000/svg" width="618" height="1000" xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 618 1000">
    <defs>
        <linearGradient id="color-text" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#30cfd0"/>
            <stop offset="100%" stop-color="#330867"/>
        </linearGradient>
        <linearGradient id="h" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#F2F2F2"/>
            <stop offset="65%" stop-color="#DBDBDB"/>
            <stop offset="100%" stop-color="#EAEAEA"/>
        </linearGradient>
        <linearGradient id="s" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#FFA8A8"/>
            <stop offset="100%" stop-color="#FCFF00"/>
        </linearGradient>
        <linearGradient id="a" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#FFA8A8"/>
            <stop offset="100%" stop-color="#FCFF00"/>
        </linearGradient>
        <linearGradient id="b" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#FFA8A8"/>
            <stop offset="100%" stop-color="#FCFF00"/>
        </linearGradient>
        <linearGradient id="c" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#FFA8A8"/>
            <stop offset="100%" stop-color="#FCFF00"/>
        </linearGradient>
        <linearGradient id="d" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#FFA8A8"/>
            <stop offset="100%" stop-color="#FCFF00"/>
        </linearGradient>
        <linearGradient id="f" gradientTransform="rotate(25)">
            <stop offset="5%" stop-color="#FFA8A8"/>
            <stop offset="100%" stop-color="#FCFF00"/>
        </linearGradient>
        <pattern id="avatar" x="0" y="0" width="100%" height="100%">
            <image  width="300" height="300" preserveAspectRatio="xMidYMin slice"/>
        </pattern>
        <pattern id="bg" x="0" y="0" width="100%" height="100%">
            <image  width="618" height="1000" preserveAspectRatio="xMidYMin slice"/>
        </pattern>
    </defs>

    <rect width="100%" height="100%" fill="url(#bg)"/>
    <rect width="100%" height="100%" fill="#000" opacity="0.4"/>
    <rect x="30" y="30" width="300" height="300" fill="url(#avt)"/>
    <text id="text-info" x="0" y="480"
          font-family="JetBrainsMono Nerd Font"
          font-weight="900"
          font-size="100"

          stroke="#DDD"
          stroke-width="2"
          fill="url(#color-text)">
        <tspan x="0" dy="0" font-size="60">Stella-rium </tspan>
        <tspan mk="time" x="0" dy="100">05:32</tspan>
        <tspan mk="star" x="0" dy="100">6.17✫</tspan>
        <tspan mk="acc" x="0" dy="100">99.4%</tspan>
        <tspan mk="pp" x="0" dy="100">373.0</tspan>
        <tspan mk="combo" x="0" dy="100">2077X</tspan>
    </text>
    <text id="text-rank" x="430" y="230"
          font-family="JetBrains Mono ExtraBold"
          font-size="260"

          stroke="#FFF"
          stroke-width="5"
          fill="url(#s)" >
        <tspan id="rank-r">S</tspan>
        <tspan id="rank-l" x="360" opacity="0">S</tspan>
    </text>

    <svg id="mods" x="30" y="350" width="558" height="70" >
    </svg>

    <svg id="pp-show" x="603">
        <rect width="18" height="1000" fill="#FDFC47"/>
        <rect mk="pp-fc" width="18" height="900" fill="#B3315F"/>
        <rect mk="pp-now" width="18" height="600"  fill="#FA742B"/>
    </svg>
</svg>

Code


import {XMLParser, XMLBuilder} from "fast-xml-parser"

const opt = {
    preserveOrder: false,
    ignoreAttributes: false,
    parseTagValue: true,
    attributeNamePrefix: "$"
}
const parser = new XMLParser(opt);
const builder = new XMLBuilder(opt);

let templateStr = readTemplate('template/sp.svg');
const template = fs.readFileSync(path, 'utf8');
console.log(JSON.stringify(template));                                         // <---- json 1
/* some work */
console.log(JSON.stringify(template));                                         // <---- json 2
builder.build(template);                                                                  // <---- error

Output

json 1

{"svg":{"defs":{"linearGradient":[{"stop":[{"$offset":"5%","$stop-color":"#30cfd0"},{"$offset":"100%","$stop-color":"#330867"}],"$id":"color-text","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#F2F2F2"},{"$offset":"65%","$stop-color":"#DBDBDB"},{"$offset":"100%","$stop-color":"#EAEAEA"}],"$id":"h","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"s","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"a","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"b","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"c","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"d","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"f","$gradientTransform":"rotate(25)"}],"pattern":[{"image":{"$width":"300","$height":"300","$preserveAspectRatio":"xMidYMin slice"},"$id":"avatar","$x":"0","$y":"0","$width":"100%","$height":"100%"},{"image":{"$width":"618","$height":"1000","$preserveAspectRatio":"xMidYMin slice"},"$id":"bg","$x":"0","$y":"0","$width":"100%","$height":"100%"}]},"rect":[{"$width":"100%","$height":"100%","$fill":"url(#bg)"},{"$width":"100%","$height":"100%","$fill":"#000","$opacity":"0.4"},{"$x":"30","$y":"30","$width":"300","$height":"300","$fill":"url(#avt)"}],"text":[{"tspan":[{"#text":"Stella-rium","$x":"0","$dy":"0","$font-size":"60"},{"#text":"05:32","$mk":"time","$x":"0","$dy":"100"},{"#text":"6.17✫","$mk":"star","$x":"0","$dy":"100"},{"#text":"99.4%","$mk":"acc","$x":"0","$dy":"100"},{"#text":373,"$mk":"pp","$x":"0","$dy":"100"},{"#text":"2077X","$mk":"combo","$x":"0","$dy":"100"}],"$id":"text-info","$x":"0","$y":"480","$font-family":"JetBrainsMono Nerd Font","$font-weight":"900","$font-size":"100","$stroke":"#DDD","$stroke-width":"2","$fill":"url(#color-text)"},{"tspan":[{"#text":"S","$id":"rank-r"},{"#text":"S","$id":"rank-l","$x":"360","$opacity":"0"}],"$id":"text-rank","$x":"430","$y":"230","$font-family":"JetBrains Mono ExtraBold","$font-size":"260","$stroke":"#FFF","$stroke-width":"5","$fill":"url(#s)"}],"svg":[{"$id":"mods","$x":"30","$y":"350","$width":"558","$height":"70"},{"rect":[{"$width":"18","$height":"1000","$fill":"#FDFC47"},{"$mk":"pp-fc","$width":"18","$height":"900","$fill":"#B3315F"},{"$mk":"pp-now","$width":"18","$height":"600","$fill":"#FA742B"}],"$id":"pp-show","$x":"603"}],"$xmlns":"http://www.w3.org/2000/svg","$width":"618","$height":"1000","$xmlns:xlink":"http://www.w3.org/1999/xlink","$viewBox":"0 0 618 1000"}}

json 2

{"svg":{"defs":{"linearGradient":[{"stop":[{"$offset":"5%","$stop-color":"#30cfd0"},{"$offset":"100%","$stop-color":"#330867"}],"$id":"color-text","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#F2F2F2"},{"$offset":"65%","$stop-color":"#DBDBDB"},{"$offset":"100%","$stop-color":"#EAEAEA"}],"$id":"h","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"s","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"a","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"b","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"c","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"d","$gradientTransform":"rotate(25)"},{"stop":[{"$offset":"5%","$stop-color":"#FFA8A8"},{"$offset":"100%","$stop-color":"#FCFF00"}],"$id":"f","$gradientTransform":"rotate(25)"}],"pattern":[{"image":{"$width":"300","$height":"300","$preserveAspectRatio":"xMidYMin slice"},"$id":"avatar","$x":"0","$y":"0","$width":"100%","$height":"100%","$xlink:href":"https://a.ppy.sh/17064371"},{"image":{"$width":"618","$height":"1000","$preserveAspectRatio":"xMidYMin slice"},"$id":"bg","$x":"0","$y":"0","$width":"100%","$height":"100%","$xlink:href":"https://assets.ppy.sh/beatmaps/1456709/covers/list@2x.jpg"}]},"rect":[{"$width":"100%","$height":"100%","$fill":"url(#bg)"},{"$width":"100%","$height":"100%","$fill":"#000","$opacity":"0.4"},{"$x":"30","$y":"30","$width":"300","$height":"300","$fill":"url(#avt)"}],"text":[{"tspan":[{"#text":"Stella-rium","$x":"0","$dy":"0","$font-size":"60"},{"#text":"05:32","$mk":"time","$x":"0","$dy":"100"},{"#text":"6.00✫","$mk":"star","$x":"0","$dy":"100"},{"#text":"95.36%","$mk":"acc","$x":"0","$dy":"100"},{"#text":"346.1","$mk":"pp","$x":"0","$dy":"100"},{"#text":"5555X","$mk":"combo","$x":"0","$dy":"100"}],"$id":"text-info","$x":"0","$y":"480","$font-family":"JetBrainsMono Nerd Font","$font-weight":"900","$font-size":"100","$stroke":"#DDD","$stroke-width":"2","$fill":"url(#color-text)"},{"tspan":[{"#text":"S","$id":"rank-r"},{"#text":"S","$id":"rank-l","$x":"360","$opacity":"0"}],"$id":"text-rank","$x":"430","$y":"230","$font-family":"JetBrains Mono ExtraBold","$font-size":"260","$stroke":"#FFF","$stroke-width":"5","$fill":"url(#s)"}],"svg":[{"$id":"mods","$x":"30","$y":"350","$width":"558","$height":"70","svg":[{"g":{"path":[{"$id":"Vector","$fill-rule":"evenodd","$clip-rule":"evenodd","$d":"M87.1349 34.8514L74.4371 56.8506H25.5628L12.865 34.8514L25.5628 12.865H74.4371L87.1349 34.8514Z","$fill":"#66CCFF"},{"$id":"Vector_2","$fill-rule":"evenodd","$clip-rule":"evenodd","$d":"M25.5628 69.7157C25.1511 69.69 24.7523 69.69 24.3406 69.6514C23.5301 69.5742 22.7197 69.4198 21.9349 69.1882C20.7642 68.8409 19.632 68.3263 18.6028 67.6701C17.5736 67.014 16.6345 66.2035 15.8369 65.2772C15.1293 64.4539 15.0006 64.1966 14.4217 63.2832L1.7239 41.2839C1.22216 40.3191 1.06778 40.0875 0.707559 39.0583C0.308743 37.9004 0.0771719 36.6911 0.0128466 35.4689C-0.0514786 34.2468 0.0771719 33.0117 0.360203 31.8281C0.553179 31.0305 0.823344 30.2586 1.15784 29.5124C1.32508 29.1393 1.53092 28.792 1.7239 28.4189L14.4217 6.43252C15.0135 5.5191 15.1293 5.2618 15.8369 4.43844C16.6345 3.51216 17.5736 2.70166 18.6028 2.04554C19.632 1.38942 20.7642 0.874823 21.9349 0.527467C22.7197 0.295896 23.5173 0.141516 24.3406 0.0643252C24.7523 0.0257301 25.1511 0.0257301 25.5628 0H74.4371C75.5178 0.0514602 75.8008 0.0257301 76.8686 0.231571C78.0779 0.463142 79.2358 0.861958 80.3293 1.42802C81.4229 1.99408 82.4263 2.70166 83.314 3.55075C83.9058 4.11681 84.4461 4.73434 84.9093 5.40332C85.1409 5.73781 85.3467 6.08517 85.5654 6.43252L98.2632 28.4189C98.4562 28.7791 98.6492 29.1393 98.8293 29.5124C99.1638 30.2586 99.4339 31.0305 99.6269 31.8281C99.9099 33.0246 100.039 34.2468 99.9743 35.4689C99.9099 36.6911 99.6784 37.9133 99.2795 39.0583C98.9193 40.0875 98.7649 40.3191 98.2632 41.2839L85.5654 63.2703C84.9736 64.1837 84.8578 64.441 84.1502 65.2644C83.3526 66.1907 82.4135 67.0012 81.3843 67.6573C80.3551 68.3134 79.2229 68.828 78.0522 69.1754C77.2674 69.4069 76.4698 69.5613 75.6465 69.6385C75.2348 69.6771 74.836 69.6771 74.4243 69.7028C58.15 69.7157 41.85 69.7157 25.5628 69.7157Z","$fill":"#FFCC22"}],"g":{"path":[{"$id":"Vector_3","$d":"M80.2908 25.4343L56.9536 65.8563H64.261L83.9445 31.7639L80.2908 25.4343Z","$fill":"#555555"},{"$id":"Vector_4","$d":"M73.1636 13.0837L42.6863 65.8562H49.9936L76.8172 19.4005L73.1636 13.0837Z","$fill":"#555555"},{"$id":"Vector_5","$d":"M67.8374 3.85962H64.2224L30.2329 62.7172L32.034 65.8563H35.7263L69.6771 7.03729L67.8374 3.85962Z","$fill":"#555555"},{"$id":"Vector_6","$d":"M35.6748 3.85962L15.9656 38.0034L19.6064 44.3331L42.9822 3.85962H35.6748Z","$fill":"#555555"},{"$id":"Vector_7","$d":"M49.9421 3.85962L23.0928 50.3668L26.7464 56.6835L57.2494 3.85962H49.9421Z","$fill":"#555555"}],"$id":"Group"},"$id":"Frame","$clip-path":"url(#clip0_2432_184)"},"defs":{"clipPath":{"rect":{"$width":"100","$height":"69.7157","$fill":"white"},"$id":"clip0_2432_184"}},"$width":"100","$height":"70","$viewBox":"0 0 100 70","$fill":"none","$xmlns":"http://www.w3.org/2000/svg","$x":"0"}]},{"rect":[{"$width":"18","$height":"1000","$fill":"#FDFC47"},{"$mk":"pp-fc","$width":"18","$height":"900","$fill":"#B3315F"},{"$mk":"pp-now","$width":"18","$height":"600","$fill":"#FA742B"}],"$id":"pp-show","$x":"603"}],"$xmlns":"http://www.w3.org/2000/svg","$width":"618","$height":"1000","$xmlns:xlink":"http://www.w3.org/1999/xlink","$viewBox":"0 0 618 1000"}}

error

TypeError: textValue.replace is not a function
    at Builder.replaceEntitiesValue (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:241:29)
    at Builder.buildTextValNode (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:225:22)
    at Builder.j2x (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:101:23)
    at Builder.processTextOrObjNode (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:155:23)
    at Builder.j2x (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:120:32)
    at Builder.processTextOrObjNode (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:155:23)
    at Builder.j2x (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:120:32)
    at Builder.processTextOrObjNode (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:155:23)
    at Builder.j2x (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:139:21)
    at Builder.processTextOrObjNode (${root}/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js:155:23)

Node.js v20.4.0

expected data

I would like to obtain a correct and attribute-containing XML.

Would you like to work on this issue?

Bookmark this repository for further updates. Visit SoloThought to know about recent features.

github-actions[bot] commented 1 year ago

We're glad you find this project helpful. We'll try to address this issue ASAP. You can vist https://solothought.com to know recent features. Don't forget to star this repo.

HollisMeynell commented 1 year ago

When encountering the issue, I noticed that the type of the textValue variable is function.

amitguptagwl commented 1 year ago

Hi @HollisMeynell

I tried to build from the json 2 and get no error.

const opt = {
    preserveOrder: false,
    ignoreAttributes: false,
    parseTagValue: true,
    attributeNamePrefix: "$"
}
const parser = new XMLParser(opt);
const builder = new XMLBuilder(opt);

// let template = fs.readFileSync( path.join(__dirname,'assets/sp.svg')).toString();
// const jsObj = parser.parse(template);  
const jsObj = require("./assets/sp_svg.json")                       // <---- json 2
builder.build(jsObj); 
HollisMeynell commented 12 months ago

Hi @HollisMeynell

I tried to build from the json 2 and get no error.

const opt = {
    preserveOrder: false,
    ignoreAttributes: false,
    parseTagValue: true,
    attributeNamePrefix: "$"
}
const parser = new XMLParser(opt);
const builder = new XMLBuilder(opt);

// let template = fs.readFileSync( path.join(__dirname,'assets/sp.svg')).toString();
// const jsObj = parser.parse(template);  
const jsObj = require("./assets/sp_svg.json")                       // <---- json 2
builder.build(jsObj); 

I've found the issue. Please try executing the following code anywhere before the builder.build(jsObj):

Object.prototype.setSvgText = function (str) {
    this["#text"] = str;
}

Modifying Object.prototype is not a good practice; it was my mistake in using it. I apologize for the inconvenience caused.