Search code examples
svgfillinkscapestroke

Is it possible to delete only the shapes that have only a fill an no stroke?


I would like to keep only the shapes that have no fill on this map : https://commons.wikimedia.org/wiki/File:Antarctica_in_the_World_(yellow).svg Obviously, I don't want to remove all the other ones by hand...

Is it possible to delete only the shapes that have only a fill an no stroke ? Maybe by editing the code of the file? I don't know how to do, but I know how to use regexes if necessary.

Thank you for your help.


Solution

  • You might use inkscape's command line but I think a custom java script is more convenient as you can immediately preview the output.

    Example 1: parse from XML markup

    1. parse the svg markup
    2. query all paths (optional: include polygons and polylines)
    3. loop: check each path's fill and stroke attribute
      3.1. remove elements without stroke
    4. get updated svg markup via new XMLSerializer()

    let markup = `<svg viewBox="0 0 100 30">
      <path  id="is" fill="#CECECE" stroke="none" d="M5.1 8.7l5.4-0.7l1.6-0.5l3.3 0.4l2.1-0.8l3.7 0.1l-2.2 1l2.2-0.4l3.8 0.5l-7.7 2.4l3.7 0.3h3.4l-12.2 1.7l-2.5-0.1l-4.2 0.1l1.4 1.3l6-0.4h3.5l4.1 0.5l-1.5 1.3l1.8 1l5.8-1.6l-2.5 0.8l-0.4 1.3l-2.2 0.7l5.6-0.5l-2.5 1.6l-2.6 0.1l-2.4 3.8l-2.1 0.1l-1-0.5l-0.5 2.1l8.1-0.4l3.8 0.1l3.1-0.8l-0.3 0.8l2.7 0.3l0.1 0.4l3.1-0.7l1 0.9l-1.4 0.3l6.4 1.3l8 1l6-0.3l2.4-0.7l1.3-1.2l3.4-0.8l4.5-1.3l6.3-0.5l7.6-2.9l5.1 0.4l2.9-2.5l1.3-1.6l5.4-2.7l-1.3-0.9l2.4-0.1l-3.6-0.7l5.6-0.4l-3.8-0.9l3.3-0.7l-3.8-0.1l3-2l-4.5-0.8l-3.8 1l2-2.4l-4.6 0.5l2-1.8l-4.6-1.4l1.2-0.9l3.7-1l-7.2 2l-3.1-1.8l-4.8-1l-2.2 0.7l-0.9 2.5l-5-1l-3.8 2.4l-7.1-2l1 1.6l1.2 2.5l-7.7-4.3l-1.7 0.9h-2.6l-0.4 2.5l-6.2-2.7l-2.1 1.8l-1.2 2.4l-2.6-0.6l-3.1 1.6l0.1 2.4l-3.6-2.4l-2.5-2.6l1.8-1.6l-0.5-0.8l-2.5-1.3l-6.4-1.8l-4.3 0.9l6 0.7l-4.5 0.8l4.1 1.4l-1.8 1.3l-3.7-1.3l0.4-0.8l-3.4-0.8l-0.4 0.7l0.4 0.9l-3.8-0.7l4.2 1.7l-4.6 0.3l5 0.7l-2.4 0.3l2.1 0.4l-7.3-0.8l2.2 1l0.4 0.8l-5.5 0.3l5.1 0.7"/>
      
      
    <path id="is_1_" fill="none" stroke="#1178AC" d="M5.1 8.7l5.4-0.7l1.6-0.5l3.3 0.4l2.1-0.8l3.7 0.1l-2.2 1l2.2-0.4l3.8 0.5l-7.7 2.4l3.7 0.3h3.4l-12.2 1.7l-2.5-0.1l-4.2 0.1l1.4 1.3l6-0.4h3.5l4.1 0.5l-1.5 1.3l1.8 1l5.8-1.6l-2.5 0.8l-0.4 1.3l-2.2 0.7l5.6-0.5l-2.5 1.6l-2.6 0.1l-2.4 3.8l-2.1 0.1l-1-0.5l-0.5 2.1l8.1-0.4l3.8 0.1l3.1-0.8l-0.3 0.8l2.7 0.3l0.1 0.4l3.1-0.7l1 0.9l-1.4 0.3l6.4 1.3l8 1l6-0.3l2.4-0.7l1.3-1.2l3.4-0.8l4.5-1.3l6.3-0.5l7.6-2.9l5.1 0.4l2.9-2.5l1.3-1.6l5.4-2.7l-1.3-0.9l2.4-0.1l-3.6-0.7l5.6-0.4l-3.8-0.9l3.3-0.7l-3.8-0.1l3-2l-4.5-0.8l-3.8 1l2-2.4l-4.6 0.5l2-1.8l-4.6-1.4l1.2-0.9l3.7-1l-7.2 2l-3.1-1.8l-4.8-1l-2.2 0.7l-0.9 2.5l-5-1l-3.8 2.4l-7.1-2l1 1.6l1.2 2.5l-7.7-4.3l-1.7 0.9h-2.6l-0.4 2.5l-6.2-2.7l-2.1 1.8l-1.2 2.4l-2.6-0.6l-3.1 1.6l0.1 2.4l-3.6-2.4l-2.5-2.6l1.8-1.6l-0.5-0.8l-2.5-1.3l-6.4-1.8l-4.3 0.9l6 0.7l-4.5 0.8l4.1 1.4l-1.8 1.3l-3.7-1.3l0.4-0.8l-3.4-0.8l-0.4 0.7l0.4 0.9l-3.8-0.7l4.2 1.7l-4.6 0.3l5 0.7l-2.4 0.3l2.1 0.4l-7.3-0.8l2.2 1l0.4 0.8l-5.5 0.3l5.1 0.7"/>
    </svg>`;
    
    // parse svg from raw xml markup
    let svg = new DOMParser()
      .parseFromString(markup, "text/html")
      .querySelector("svg");
    
    // query all paths and similar elements
    let paths = svg.querySelectorAll("path, polygon, polyline");
    paths.forEach((path) => {
      // check attributes
      let stroke = path.getAttribute("stroke");
      let fill = path.getAttribute("fill");
      
      // remove if element doesn't have stroke but a fill
      if ((fill || fill!='none') && (!stroke || stroke==='none') ) {
        path.remove();
      }
    });
    
    // get updated svg
    let markupNew = new XMLSerializer().serializeToString(svg)
    console.log(markupNew)
    
    
    // render preview
    preview.append(svg);
    svg{
      border:1px solid #ccc;
      overflow:visible
      width: 50%;
      height: auto;
    }
    <div id="preview"></div>

    Example 2: parse from XML fetched (remote) svg file

    This approach requires the svg source to allow cross origin access. So the svg needs to have a appropriate CORS header or to be served from same domain.

    /**
     * fetch data asynchronously
     */
    (async() => {
      let url = "https://upload.wikimedia.org/wikipedia/commons/b/b6/Antarctica_in_the_World_%28yellow%29.svg"
      let fetchedData = await (fetch(url));
      let markup = await fetchedData.text()
      // remove tabs and newlines
      markup = markup.replaceAll('\t', '').replace(/[\n\r\t]/g, "")
    
      // parse svg
      let svg = new DOMParser().parseFromString(markup, 'text/html').querySelector('svg')
    
      /**
       * cleanup:
       * remove AI metadata 
       * and forreignObjects
       */
      let remove = svg.querySelectorAll('foreignObject, #adobe_illustrator_pgf')
      remove.forEach(el => {
        el.remove()
      })
    
      // unwrap switch
      let switchEl = svg.querySelector('switch');
      let switchCnt = switchEl.children[0]
      switchEl.parentNode.insertBefore(switchCnt, switchEl)
      switchEl.remove()
    
      // query all paths and similar elements
      let paths = svg.querySelectorAll('path, polygon, polyline')
      paths.forEach(path => {
        // check attributes
    
        let stroke = path.getAttribute('stroke')
        let fill = path.getAttribute('fill')
        // remove if element doesn't have stroke but a fill
        if ((fill || fill != 'none') && (!stroke || stroke === 'none')) {
          path.remove()
        }
      })
    
      // remove empty groups
      let groups = svg.querySelectorAll('g')
      groups.forEach(g => {
        if (!g.querySelectorAll('path, polygon, rect, circle, ellipse, line, polyline, text').length) {
          g.remove()
        }
      })
    
    
      // render preview
      preview.append(svg)
    
      // cleanup whitespace and create download
      let markupNew = new XMLSerializer().serializeToString(svg)
        .replace(/\ {2,}/g, " ")
        .replace(/[\n\r\t]/g, "")
        .replaceAll('>', '>\n')
    
      let blob = new Blob([markupNew], {
        type: 'image/svg+xml'
      })
      output.value = markupNew;
      fileSize.textContent = +(blob.size / 1024 / 1024).toFixed(3) + ' KB'
      btnDownload.href = URL.createObjectURL(blob)
    
    
    })()
    svg {
      width: 100%;
      height: auto;
    }
    
    textarea {
      width: 100%;
      min-height: 10em;
      display: block;
    }
    <p><a id="btnDownload" href="" download="new.svg">Download</a> <span id="fileSize"></span> </p>
    <div id="preview"></div>
    <h3>Output</h3>
    <textarea id="output"></textarea>

    How it works

    1. we're fetching the svg file
    2. parse it's markup via new DOMParser()
    3. query all path and polygon elements (quite often shapes in maps can either be paths or polygons)
    4. loop through all path elements and check their fill and stroke attributes via element.getAttribute(): if a path has a fill but no stroke attribute we can remove this item
    5. create a blob and an object URL to add a download link (doesn't work in SO snippets)

    Cleanup (optional)

    Besides, we can remove some proprietary Adobe Illustrator metadata (used for preview images, editor settings and assets etc.).
    We can also remove empty groups or unused definitions.
    This way we can significantly reduce file size.

    Testing: codepen example