Issues while trying to implement template handling

Previous topic - Next topic

michaelu

Hi,
I am trying to implement a template mechanism in a script. The idea is to have a text containing placeholders like ${date}, $[title}, ${name}, plus a data set containing dates, titles and names, and create for each data a copy of the text with the placeholders replaced by the actual data. The trick is now that the text features such as font, font size, text color, line spacing, tabs etc. shall be preserved as faithfully as possible. My current findings are, that this is not possible in general, but that we can come close. The main obstacle is that there is not enough functionality in the script API (at least I was unable to find it). For example, there is no function getCharacterStyle. Or you can get style names (getAllStyles), but you cannot get style details. Only today I found a getStyle() in 1.5.6, which did not yet exist in 1.5.2, but which returns only paragraph styles.

So far I came up with this code:

textlen = scribus.getTextLength(textbox)
scribus.selectText(0, 1, textbox)
last_style = scribus.getStyle(textbox)
last_fontname = scribus.getFont(textbox)
last_fontsize = scribus.getFontSize(textbox)
last_textcolor = scribus.getTextColor(textbox)
last_textshade = scribus.getTextShade(textbox)
text = ""

pos = textlen
changed = False

for c in range(0, textlen):
    scribus.selectText(c, 1, textbox)
    char = scribus.getText(textbox)
    style = scribus.getStyle(textbox)
    if style != last_style:
        changed = True
    fontname = scribus.getFont(textbox)
    if fontname != last_fontname:
        changed = True
    fontsize = scribus.getFontSize(textbox)
    if fontsize != last_fontsize:
        changed = True
    textcolor = scribus.getTextColor(textbox)
    if textcolor != last_textcolor:
        changed = True
    textshade = scribus.getTextShade(textbox)
    if textshade != last_textshade:
        changed = True
    if changed:
        scribus.insertText(text, pos, textbox)
        tlen = scribus.getTextLength(textbox)
        scribus.selectText(pos, tlen - pos, textbox)
        scribus.setStyle(noStyle if last_style is None else last_style, textbox)
        scribus.selectText(pos, tlen - pos, textbox)
        scribus.setFont(last_fontname, textbox)
        scribus.selectText(pos, tlen - pos, textbox)
        scribus.setFontSize(last_fontsize, textbox)
        scribus.selectText(pos, tlen - pos, textbox)
        scribus.setTextColor(last_textcolor, textbox)
        scribus.selectText(pos, tlen - pos, textbox)
        scribus.setTextShade(last_textshade, textbox)
        pos = tlen
        text = ""
        last_fontname = fontname
        last_fontsize = fontsize
        last_textcolor = textcolor
        last_textshade = textshade
        last_style = style
        changed = False
    text = text + char


As you can see, I go through the text one character at a time, collecting all characters with the same formatting, as far as I can determine it, then insert  the text at the end, and applying the formatting as much as possible.
Some remarks:
- I had to repeat the selectText before each set..., apparently the set...-functions unselect the current selection
- I had to specify the second parameter textbox to avoid error messages
- The template contains special characters. Adding len(text) to pos did not work, because Scribus character count is not the same as Python character len. So, because a getCurrentPos() is missing, and insertText does not return the last pos, I use getTextLength to go to the end of the inserted text. This works of course only if you do not start with a position in the middle of text.
- I found no way to getText text that I had inserted with insertText. The text length is increased luckily, and you can selectText the new text, but you cannot getText it. getText returns only an empty string for the new text.
- We can obtain font, font size, color, shade, and paragraph style. But when you look at the story editor, you can do more to text, e.g. underline it. You can also apply a character style to piece of text (select in the main window, F3). But I could not find out (yet) how to get at these properties. So the replicated text looses e.g. underlining. It does not loose font or font size, if the character style changed that from the paragraph style.



a.l.e



a.l.e

ah, no... there is only getCharStyles() ...

since now there is getStyle() for paragraph styles, it should not be hard to add getCharStyle()...

... from what i understood from your description, it's likely that you will need to add or tweak other scripter commands, so i dare to recommend you to have a try at adding getCharStyle().

it should be simple enough (i can of course give you a few other hints) for a first scribus contribution...

a.l.e

if you want some interactive help, you might want to connect to the #scribus channel on irc or the matrix.org scribus channel...

noobjuk

Was there a follow up to this for latest version. I too am trying to update a block of text and trying to preserve the original formatting using a command line python script - why is it so difficult as I would've thought that would've been a bread and butter function?

a.l.e

hi,

exporting with formatting (and importing from it again), might be doable for text frames that consistently (and rather simply) structured.

for general (and more complex) use cases, you found the reason why scribus does not allow to link text frames to the sources but imports the text : - )

p.s.: you have probably found out that getCharStyle() now exists...

noobjuk

I've tried to do scribus.insertText(_newContent, 0, _textFrame) and if the lines of content contains '\n' right after a punctuation mark then that punctuation ends up at the beginning of the line e.g.

John sat on a hill, \n then went to town \n then jumped up and down. \n

it would appear as:

,John sat on a hill
then went to town
.then jumped up and down

a.l.e

cannot reproduce (in the scripter console):

scribus.insertText("John sat on a hill,\nthen went to town\nthen jumped up and down.\n", 0)
works as expected (when a text frame is selected)

btw, you don't want spaces before and after the \n...

noobjuk

#9
Thank you so much for trying it out but I have a setup like below. I'm absolutely new and trying to do it programmatically. 

I want to run a script and change the text frame content. I can only see a convoluted way of doing it with the scripts below.

I can select the text frames and add in text while preserving the formatting.  Punctuation spills over.  I can't see what I'm doing wrong? Please help :)


#This code lives in the scribusReplaceText.py file and is run from a script
import scribus
from datetime import datetime

# replace the text within an existing textframe
def ReplaceText(_textFrame, _newContent):

    existingTextLength = scribus.getTextLength(_textFrame)

    # use example content
    _newContent = "John sat on a hill,\nthen went to town\nthen jumped up and down.\n"
  
    # clear the text frame of existing placeholder text -
    # placeholder text has specific formatting applied which will carry over to new content
    scribus.selectText(0, existingTextLength-1, _textFrame)
    scribus.deleteText(_textFrame)
    scribus.insertText(_newContent, 0, _textFrame)

    # clear up remnants of old text at the end
    newTextLength = scribus.getTextLength(_textFrame)
    scribus.selectText(newTextLength-1, 1, _textFrame)
    scribus.deleteText(_textFrame)


def CreatePdf():
    pdf = scribus.PDFfile()
    pdf.file = 'output1.pdf'
    pdf.save()
    print("*** pdf created: ")


ReplaceText("Text1", "")
CreatePdf()


I don't know if it makes a difference but I'm running the script from a command line



# This script executes scribus to run a python script to change text frame content

import subprocess

    # Run Scribus using command line to execute python
    command = "/Applications/Scribus.app/Contents/MacOS/Scribus -g -py scribusReplaceText.py"

    # Use subprocess to run the command
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # Get the output and error (if any) from the command
    output, error = process.communicate()

Hang on don't answer this yet as I'm updating this question.

noobjuk

I've found that if I use a new SLA file it seems to work ok.