Inkscape.org
Beyond the Basics Tspans renumbering automatically
  1. #1
    JeremyGraeme JeremyGraeme @JeremyGraeme

    Hi there. 
     

    I've created a form as an SVG where I use the tspan Ids to locate nodes and replace text  / images in the nodes.  This works fine and well, however I'm seeing a situation where when I edit my svg in inkscape again, and make, say, a cosmetic change, it automatically replaces EVERY ONE of my tspan ids with automatic sequential tspans.  How do I stop this behavior from occurring or what alternate method should I be using to locate fields for text replacement on the form?

     

    Thanks

    J

  2. #2
    inklinea inklinea @inklinea⛰️

    SVG uses tspans to allow different styles and placement inside the same <text> element.

    It might be that the ids are not changing, but the tspans are being destroyed and recreated ? 

    You can safely tag any element in an svg using the data-* attribute, but it might not help if the tspans are being replaced.

    data-* and its values can be retrieved using standard query selectors / xpath etc.

  3. #3
    JeremyGraeme JeremyGraeme @JeremyGraeme

    The truly weird thing is, it replaces some but not all of the tspans.  There have been some set with IDs that have not changed and have not been replaced whatsoever. 

  4. #4
    JeremyGraeme JeremyGraeme @JeremyGraeme

    Awesome, thanks!  I'll look into both of those solutions and see which one works best!

  5. #5
    JeremyGraeme JeremyGraeme @JeremyGraeme

    Huh.  Yep, turns out even if I put the data-field in the tspan alongside the ID, it completely blows it away every time.   It destroys and remakes the whole tspan object.  That's very unfortunate.

  6. #6
    inklinea inklinea @inklinea⛰️

    Do you need the <tspans> for what you are doing ?

    You can work with <text> only if you add a text node.

    If what you are doing is simple, you could use multiple <text> elements instead ?

  7. #7
    JeremyGraeme JeremyGraeme @JeremyGraeme

    It's pretty complex, and yeah, we can use multiple text nodes, but even within those, inkscape adds tspans even if the text hasn't moved or anything.  It just turns everything into an node list of tspans inside the text elements.  If there's a way we can convert those tspans into text elements, that'd work.  Make the document significantly larger but should get it done.

     

    There are probably 70 text fields per page here, and every time I turn them into texts only, they get <tspan'd automatically, again with different ids.

     

     

     

     

  8. #8
    inklinea inklinea @inklinea⛰️

    Can you show a workflow example of what you are trying to do ? 

  9. #9
    JeremyGraeme JeremyGraeme @JeremyGraeme

    So, the workflow example I'm aiming for is that we create a form in inkscape and have the items within the form filled in via Java and XML arrangement, then converting that svg to a PDF.

     

    The problem I'm having is that tspans are all getting renumbered, and all the text elements are getting an arbitrary number of tspans when I create a new text node.  No matter what, when I add text to a box / rect, I get the text element, but underneath that is an automatically created tspan.   The tspan varies and changes, does not respect the data-field or any added attribute, and then when I save again in inkscape, deletes and replaces the whole thing.   This is, as you can imagine, problematic.  Sometimes, also, text fields do NOT create any tspans.  So I can't just take the first child node of the text element hoping it is a tspan, because sometimes the arbitrary number of tspans that gets created is 0 and the text is attached directly to the text node.

  10. #10
    inklinea inklinea @inklinea⛰️

    I've attached a ZIP file to this post with the example extension.

    If you are using python in an extension;

    (You could save directly from the extension or a make an output extension)

    Assuming that we don't care about any special formatting in the <tspan> itself ?

    Assuming that the styling is on the <text> element not the <tspan> ( although it's simple to copy the style from child tspans to parent element)

    Maybe this would be a solution using xpath:

    *** However this does not account for if you are using tspans to simulate spaces or newlines. So may only work for a single tspan, I don't know.

    I've made an example that will extract text from all tspans in the text element and either:

    - Destroy all tspans and dump the text into the parent <text> element

    or 

    - Create a new single child <tspan> and dump the text in that.

    In both cases and text directly in the <text> element will be erased to prevent duplication.

    You just need to change the tspan_bool value in:

    tspans_to_text(self, selection, f'tspan{count}', tspan_bool=True)

    To choose one of the two methods.

    Also for the id of the tspan, you may wish to grab it from the parent text instead to avoid id collisions or use something totally different

    tspan_id = f'tspan_{text_element.get_id()}'  -- something like that.

    ----------------------
    The resulting svg is also saved to your temp folder with a random filename.
    -----------------------

    Anyway, that was my best idea for today :)
        

    ---------------------------------------------------------------

     

    import inkex
    from inkex import Tspan
    
    from uuid import uuid4
    from tempfile import gettempdir
    import os
    
    def tspans_to_text(self, text_element, tspan_id_string, tspan_bool):
        my_text_list = text_element.xpath('.//text()')
        inkex.errormsg(my_text_list)
    
        for child in text_element.getchildren():
            text_element.remove(child)
    
        text_element.text = ''
    
        my_text = ''.join(my_text_list)
    
        if not tspan_bool:
            text_element.text = my_text
        else:
            new_tspan = Tspan()
            new_tspan.set('id', tspan_id_string)
            new_tspan.text = my_text
            text_element.append(new_tspan)
    
    def write_svg_file(svg, filename):
    
        debug_temp_filepath = os.path.join(str(gettempdir()), filename)
    
        with open(debug_temp_filepath, mode='w', encoding='utf-8') as file:
            file.write(str(svg.tostring().decode('utf-8')))
            file.close()
    
    class TspanRenew(inkex.EffectExtension):
    
        def add_arguments(self, pars):
            pass
        
        def effect(self):
            pass
            
            selection_list = self.svg.selected
            if len(selection_list) < 1:
                inkex.errormsg('Please select at least one object')
                return
                
            for count, selection in enumerate(selection_list.filter(inkex.TextElement)):
                inkex.errormsg(f'{selection} {selection.get_id()}')
    
                tspans_to_text(self, selection, f'tspan{count}', tspan_bool=True)
    
            write_svg_file(self.svg, f'{str(uuid4())}.svg')
            
    if __name__ == '__main__':
        TspanRenew().run()
    
  11. #11
    JeremyGraeme JeremyGraeme @JeremyGraeme

    Oh awesome, thank you so much!  That helps immensely and gets me pretty much where I needed to go.

     

    Very much appreciated!  Thanks!

     

    J

Inkscape Inkscape.org Inkscape Forum Beyond the Basics Tspans renumbering automatically