Autoquote2

From Scribus Wiki
Jump to: navigation, search
This article is part of the Scripts series.

I have changed the logic of the original, Autoquote.py, so that it is much simpler, and therefore probably easier to tweak to your needs. Here is the basic process for each kind of typographic quotation:

  • I have created a couple of flags, doublequote_flag and singlequote_flag, which merely denote whether you are outside or inside a set of quotation marks.
  • If you are outside, the next quote is a leading quote, if inside, the next is a following quote. Since the single and double flags are separate, nested quotes are possible, but only one layer. In other words, for example, you will not get a good result if you put double quotes inside single quotes inside double quotes. I think this is an unlikely occurrence, and suggests you're overusing quotes.
  • There is also the addition of an apostrophe, for contractions like don't or d'etat. The apostrophe is a single follow quote – there is an alternative built in by commenting out one line and uncommenting the next to stay with a typewriter single quote. The test is whether the previous and next character are both alphanumeric.

Notes

  • Something not handled is a contraction like 'twas or 'tween. Also not handled at this time is the single guillemet, if this is used in nested quotes, the script can be modified. It's not clear to me how useful single guillemets are in French on a practical level, so the script uses English-style single quotes for now. There is a complexity with French that might require 'fr1' and fr2' choices, but one option seems to be double guillemets inside double guillemets, which would require a different approach.
  • In French, a non-breaking space is added after the leading double guillemets and before following double guillemets. Adding these spaces causes a counting problem in the number of characters in the frame, and may lead to a failure to get to the last character, in case that is a quotation mark. The work-around, if you see this is to run the script again on the same frame, and set the flag to inside as shown below – you should have gotten a message that the script ended with the flag inside for one kind of quote.
  • You should check the font for the typographical quotes after they are inserted to make sure they make the font of the frame originally. It took quite a bit of experimentation such as changing the default font for text frames in Preferences and in Document Setup (neither worked), but finally here is how to anticipate this: the inserted typographic quotes will have the font of the Default Paragraph Style, so simply check that and change if necessary before you run the script.

Linked Frames

If you are running this in one of a series of linked frames, the script will only process one frame at a time, so it would need to be run on each frame in the series. This may end up with open quotes at the end of the frame. Thus there is a message box at the end giving you this information. The other work around could be to alter the script so it could run on a text file outside of Scribus – you would need to do this before you do your layout, since formatting would be lost.

If either of these says inside when the script ends, then either there is a mistake in the text, you have the case of French where the non-breaking spaces have been added, or you have a linked frame and are truly in the middle of a quote. When you run the script for the next frame in the series enter this for the state of the double or single quote flag. If you make a mistake and misspell outside or inside, the script chooses the default outside.

Autoquote dialog.png
Autoquote dialog1.pngAutoquote dialog2.png These dialogs actually come earlier in the script, but in almost all situations the first time you run it, just click Ok for each. This is where you might use the information you get from the last time you ran it.

An Option

Some languages, French and Russian for example, may use the same typographic quotes for both layers. Here is a variation for this situation. Note that Russian does not typically put a space between the double guillemets and its internal text.

  elif (lang == 'ru2'): # this variant will use double guillemets for both layers of nested quotes
    lead_double = u"\u00ab"
    follow_double = u"\u00bb"
    lead_single = u"\u00ab"
    follow_single = u"\u00bb"

You would still need to use a single quote inside (perhaps there will be another solution later).

Пушкин писал Дельвигу: "Жду 'Цыганов' и тотчас тисну".

becomes:

Пушкин писал Дельвигу: «Жду «Цыганов» и тотчас тисну».

(Quote borrowed from Wikipedia – see link above)

The Script

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: Autoquote2.py - changes typewriter quotes to typographic quotes
# © 2010.12.26 Gregory Pittman
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
"""
USAGE

You must have a document open, and a text frame selected.
There will be a valueDialog asking for your language for the quotes, 
the default is 'en', but change the default to suit your needs.
Detected errors shut down the script with an appropriate message.

"""
import scribus

if scribus.haveDoc():
    c = 0
    lang = scribus.valueDialog("Choose by language or country", 'Language: af, be, ch, de, en, es, et, fi, fr,\n hu, is, lt, mk, nl, pl, ru, se, sk, sl, sq and uk\n are current choices','en')
    if (lang == 'en'):
        lead_double = u"\u201c"
        follow_double = u"\u201d"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif (lang == 'de'):
        lead_double = u"\u201e"
        follow_double = u"\u201c"
        lead_single = u"\u2019"
        follow_single = u"\u201a"
    elif (lang == 'fr'):      
        lead_double = u"\u00ab" + u"\u00a0" # Unicode 00a0 is a non-breaking space
        follow_double = u"\u00a0" + u"\u00bb"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif (lang == 'pl'):
        lead_double = u"\u201e"
        follow_double = u"\u201d"
        lead_single = u"\u201a"
        follow_single = u"\u2019"
    elif ((lang == 'se') or (lang == 'fi')):
        lead_double = u"\u201d"
        follow_double = u"\u201d"
        lead_single = u"\u2019"
        follow_single = u"\u2019"
    elif (lang == 'af'):
        lead_double = u"\u201c"
        follow_double = u"\u201d"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif (lang == 'sq'):
        lead_double = u"\u201e"
        follow_double = u"\u201c"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif ((lang == 'be') or (lang == 'ch') or (lang == 'uk') or (lang == 'ru')):
        lead_double = u"\u00ab"
        follow_double = u"\u00bb"
        lead_single = u"\u2039"
        follow_single = u"\u203a"
    elif (lang == 'uk'):
        lead_double = u"\u00ab"
        follow_double = u"\u00bb"
        lead_single = u"\u2039"
        follow_single = u"\u203a"
    elif (lang == 'es'):
        lead_double = u"\u00ab"
        follow_double = u"\u00bb"
        follow_double = u"\u201d"
        lead_single = u"\u2018"
    elif ((lang == 'lt') or (lang == 'mk') or (lang == 'is') or (lang == 'sk') or (lang == 'sl') or (lang == 'et')):
        lead_double = u"\u201e"
        follow_double = u"\u201c"
        lead_single = u"\u2019"
        follow_single = u"\u201a"
    elif ((lang == 'hu') or (lang == 'nl')):
        lead_double = u"\u201e"
        follow_double = u"\u201d"
        lead_single = u"\u00bb"
        follow_single = u"\u00ab"
    else:
        scribus.messageBox('Language Error', 'You need to choose an available language', icon=0, button1=1)
        sys.exit(2)
        
else:
    scribus.messageBox('Usage Error', 'You need a Document open', icon=0, button1=1)
    sys.exit(2)

if scribus.selectionCount() == 0:
    scribus.messageBox('Scribus - Usage Error',
        "There is no object selected.\nPlease select a text frame and try again.",
        scribus.ICON_WARNING, scribus.BUTTON_OK)
    sys.exit(2)
if scribus.selectionCount() > 1:
    scribus.messageBox('Scribus - Usage Error',
        "You have more than one object selected.\nPlease select one text frame and try again.", scribus.ICON_WARNING, scribus.BUTTON_OK)
    sys.exit(2)
textbox = scribus.getSelectedObject()
pageitems = scribus.getPageItems()
boxcount = 1
for item in pageitems:
    if (item[0] == textbox):
        if (item[1] != 4):
            scribus.messageBox('Scribus - Usage Error', "This is not a textframe. Try again.", scribus.ICON_WARNING, scribus.BUTTON_OK)
            sys.exit(2)
contents = scribus.getTextLength(textbox)
doublequote_flag = scribus.valueDialog("Beginning Double Quotation State", "'outside' is normal. If you are running on a linked frame,\n enter 'inside' if this was indicated at last script end",'outside')
singlequote_flag = scribus.valueDialog("Beginning Single Quotation State", "'outside' is normal. If you are running on a linked frame,\n enter 'inside' if this was indicated at last script end",'outside')
if ((doublequote_flag != 'outside') and (doublequote_flag != 'inside')): doublequote_flag  = 'outside'
if ((singlequote_flag != 'outside') and (singlequote_flag != 'inside')): singlequote_flag  = 'outside'

apostrophe = u"\u2019" # we are using a follow single curly quote for apostrophe
#apostrophe = "'" # uncomment this line (delete the '#' at the beginning) to stay with a typewriter quote for apostrophe
while 1:
    if (c == contents): break
    if ((c + 1) > contents - 1):
        nextchar = ' '
    else:
        scribus.selectText(c+1, 1, textbox)
        nextchar = scribus.getText(textbox)
    scribus.selectText(c, 1, textbox)
    char = scribus.getText(textbox)
    if (len(char) != 1):
        c += 1
        continue
    if ((ord(char) == 34) and (doublequote_flag == 'outside')):
        scribus.deleteText(textbox)
        scribus.insertText(lead_double, c, textbox)
        doublequote_flag = 'inside'
    elif ((ord(char) == 34) and (doublequote_flag == 'inside')):
        scribus.deleteText(textbox)
        scribus.insertText(follow_double, c, textbox)
        doublequote_flag = 'outside'
            
    if ((ord(char) == 39) and (c == 0)):
        scribus.deleteText(textbox)
        scribus.insertText(lead_single, c, textbox)
        singlequote_flag = 'inside'
    elif (ord(char) == 39):
        if ((prevchar.isalnum()) and (nextchar.isalnum())):
            scribus.deleteText(textbox)
            scribus.insertText(apostrophe, c, textbox)
        elif (singlequote_flag == 'inside'):
            scribus.deleteText(textbox)
            scribus.insertText(follow_single, c, textbox)
            singlequote_flag = 'outside'
        elif (singlequote_flag == 'outside'):
            scribus.deleteText(textbox)
            scribus.insertText(lead_single, c, textbox)
            singlequote_flag = 'inside'
            
    c += 1
    prevchar = char
    contents = scribus.getTextLength(textbox)

scribus.setRedraw(1)
scribus.docChanged(1)
endmessage = 'Successfully ran script\nThe final state of Double Quotes: '+ doublequote_flag + '\nThe final state of Single Quotes: '+ singlequote_flag # Change this message to your liking
scribus.messageBox("Finished", endmessage,icon=0,button1=1)

Autoquote3.py

Here is another variant of the script. It was mentioned to me that in some languages such as Russian, one may want to use only double guillemets for the nested quotations. So with a little added logic, and the expansion of the doublequote_flag to have values of outside, inside, and insideinside, this is now possible. If it isn't clear what this means, look at the following examples (sorry for the fact they're not in Russian and French).

Autoquote example.png

Here we have used only typewriter double quotes even though butter is nested inside the outer quotes. Next we run the script, and choose 'ru' for the language, and we get:

Autoquote example1.png

Or the French style here, and of course, because of the added spaces we needed to run the script a second time, specifying 'inside' for the Beginning Double Quote State (see above) to capture that last double guillemet at the end.

Autoquote example2.png

This creates another potential issue, in that this could easily end up with two sets of guillemets in a row, which typically seem to be contracted to one. The answer might come from a separate script looking for these side by side twin guillemets and eliminating them, or not inserting the second as the script runs. It should be mentioned that one failing this script will have is when there might be two leading double quotes side by side, since the test for making a second leading double quote relies on there being a space in front of the second one.

If there is an advantage of these various options, it allows for getting text material from various sources, where the typist may have used different characters as substitutes for the actual typographic glyphs.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: Autoquote3.py - changes typewriter quotes to typographic quotes
# © 2010.12.25 Gregory Pittman
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
"""
USAGE

You must have a document open, and a text frame selected.
There will be a valueDialog asking for your language for the quotes, 
the default is 'en', but change the default to suit your needs.
Detected errors shut down the script with an appropriate message.

This version allows for a couple of things:

a) when choosing French (fr) for the language, a non-breaking space is
   inserted between the double guillemets and the internal text.*

b) one can also use 2 layers of double quotes, as is an option in Russian
   and French.

* Note that if you choose French, these added characters mess up the counting 
of characters, so that if there are any quotes near the end, they may not be
transformed. Simply run the script on the frame again.

You should check the font for the typographical quotes after they are inserted
to make sure they make the font of the frame originally. The inserted 
typographic quotes will have the font of the Default Paragraph Style,
so simply check that and change if necessary before you run  the script.

"""
import scribus

if scribus.haveDoc():
    c = 0
    lang = scribus.valueDialog("Choose by language or country", 'Language: af, be, ch, de, en, es, et, fi, fr,\n hu, is, lt, mk, nl, pl, ru, se, sk, sl, sq and uk\n are current choices','en')
    if (lang == 'en'):
        lead_double = u"\u201c"
        follow_double = u"\u201d"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif (lang == 'de'):
        lead_double = u"\u201e"
        follow_double = u"\u201c"
        lead_single = u"\u2019"
        follow_single = u"\u201a"
    elif (lang == 'fr'):      
        lead_double = u"\u00ab" + u"\u00a0"
        follow_double = u"\u00a0" + u"\u00bb"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif (lang == 'pl'):
        lead_double = u"\u201e"
        follow_double = u"\u201d"
        lead_single = u"\u201a"
        follow_single = u"\u2019"
    elif ((lang == 'se') or (lang == 'fi')):
        lead_double = u"\u201d"
        follow_double = u"\u201d"
        lead_single = u"\u2019"
        follow_single = u"\u2019"
    elif (lang == 'af'):
        lead_double = u"\u201c"
        follow_double = u"\u201d"
        lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif (lang == 'sq'):
        lead_double = u"\u201e"
        follow_double = u"\u201c"
    lead_single = u"\u2018"
        follow_single = u"\u2019"
    elif ((lang == 'be') or (lang == 'ch') or (lang == 'uk') or (lang == 'ru')):
        lead_double = u"\u00ab"
        follow_double = u"\u00bb"
        lead_single = u"\u2039"
        follow_single = u"\u203a"
    elif (lang == 'uk'):
        lead_double = u"\u00ab"
        follow_double = u"\u00bb"
        lead_single = u"\u2039"
        follow_single = u"\u203a"
    elif (lang == 'es'):
        lead_double = u"\u00ab"
        follow_double = u"\u00bb"
    follow_double = u"\u201d"
        lead_single = u"\u2018"
    elif ((lang == 'lt') or (lang == 'mk') or (lang == 'is') or (lang == 'sk') or (lang == 'sl') or (lang == 'et')):
        lead_double = u"\u201e"
        follow_double = u"\u201c"
        lead_single = u"\u2019"
        follow_single = u"\u201a"
    elif ((lang == 'hu') or (lang == 'nl')):
    lead_double = u"\u201e"
    follow_double = u"\u201d"
    lead_single = u"\u00bb"
    follow_single = u"\u00ab"
    else:
        scribus.messageBox('Language Error', 'You need to choose an available language', icon=0, button1=1)
        sys.exit(2)
        
else:
    scribus.messageBox('Usage Error', 'You need a Document open', icon=0, button1=1)
    sys.exit(2)

if scribus.selectionCount() == 0:
    scribus.messageBox('Scribus - Usage Error',
        "There is no object selected.\nPlease select a text frame and try again.",
        scribus.ICON_WARNING, scribus.BUTTON_OK)
    sys.exit(2)
if scribus.selectionCount() > 1:
    scribus.messageBox('Scribus - Usage Error',
        "You have more than one object selected.\nPlease select one text frame and try again.", scribus.ICON_WARNING, scribus.BUTTON_OK)
    sys.exit(2)
textbox = scribus.getSelectedObject()
pageitems = scribus.getPageItems()
boxcount = 1
for item in pageitems:
    if (item[0] == textbox):
        if (item[1] != 4):
            scribus.messageBox('Scribus - Usage Error', "This is not a textframe. Try again.", scribus.ICON_WARNING, scribus.BUTTON_OK)
            sys.exit(2)
contents = scribus.getTextLength(textbox)
doublequote_flag = scribus.valueDialog("Beginning Double Quotation State", "'outside' is normal. If you are running on a linked frame,\n enter 'inside' if this was indicated at last script end",'outside')
singlequote_flag = scribus.valueDialog("Beginning Single Quotation State", "'outside' is normal. If you are running on a linked frame,\n enter 'inside' if this was indicated at last script end",'outside')
if ((doublequote_flag != 'outside') and (doublequote_flag != 'inside')): doublequote_flag  = 'outside'
if ((singlequote_flag != 'outside') and (singlequote_flag != 'inside')): singlequote_flag  = 'outside'

apostrophe = u"\u2019" # we are using a follow single quote for apostrophe
while 1:
    if (c == contents): break
    if ((c + 1) > contents - 1):
        nextchar = ' '
    else:
        scribus.selectText(c+1, 1, textbox)
        nextchar = scribus.getText(textbox)
    scribus.selectText(c, 1, textbox)
    char = scribus.getText(textbox)
    if (len(char) != 1):
        c += 1
        continue
    if ((ord(char) == 34) and (doublequote_flag == 'outside')):
        scribus.deleteText(textbox)
        scribus.insertText(lead_double, c, textbox)
        doublequote_flag = 'inside'
    elif ((ord(char) == 34) and (doublequote_flag == 'inside') and (prevchar == ' ')):
        scribus.deleteText(textbox)
        scribus.insertText(lead_double, c, textbox)
        doublequote_flag = 'insideinside'
    elif ((ord(char) == 34) and (doublequote_flag == 'insideinside')):
        scribus.deleteText(textbox)
        scribus.insertText(follow_double, c, textbox)
        doublequote_flag = 'inside'
    elif ((ord(char) == 34) and (doublequote_flag == 'inside')):
        scribus.deleteText(textbox)
        scribus.insertText(follow_double, c, textbox)
        doublequote_flag = 'outside'

    if ((ord(char) == 39) and (c == 0)):
        scribus.deleteText(textbox)
        scribus.insertText(lead_single, c, textbox)
    singlequote_flag = 'inside'
    elif (ord(char) == 39):
        if ((prevchar.isalnum()) and (nextchar.isalnum())):
            scribus.deleteText(textbox)
            scribus.insertText(apostrophe, c, textbox)
        elif (singlequote_flag == 'inside'):
            scribus.deleteText(textbox)
            scribus.insertText(follow_single, c, textbox)
            singlequote_flag = 'outside'
        elif (singlequote_flag == 'outside'):
            scribus.deleteText(textbox)
            scribus.insertText(lead_single, c, textbox)
            singlequote_flag = 'inside'
            
    c += 1
    prevchar = char
    contents = scribus.getTextLength(textbox)

scribus.setRedraw(1)
scribus.docChanged(1)
endmessage = 'Successfully ran script\nThe final state of Double Quotes: '+doublequote_flag+ '\nThe final state of Single Quotes: '+singlequote_flag # Change this message to your liking
scribus.messageBox("Finished", endmessage,icon=0,button1=1)