Formatxml

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

Import xml and apply styles using formatxml

This script places the content of a xml file in a selected Scribus document.
The content is every text node including ignorable whitespace.

The content is placed in a text frame that is created automatically in the selected Scribus document.
The dimensions of the text frame are based on the live area of the document. That is the size of the page minus the margins.
New pages with connected text frames will be created while the current text frame overflows.

Upon execution the script will prompt for three files:

  1. the xml file that will be imported;
  2. the scribus template (an empty document);
  3. the location where the document should be saved.

When the script is done the document will be saved and closed.

Apply a style to the content of an element

The script handles two attributes in the namespace "http://www.scribus.net/formatxml":

  • style
    The value is a style to be applied to the content of the element.
  • override
    The value is a font to be applied to the content of an element.

In the following example the style of the title of a (very short) chapter is set to "header 1" and the style of the para's is set to "body". The contents of the <emphasis> is set in "Verdana Italic".

<?xml version="1.0"?>
<chapter xmlns:sla="http://www.scribus.net/formatxml"><title sla:style="header 1">The title of the chapter</title>
<para sla:style="body">Lorem ipsum dolor sit amet, <emphasis sla:override="Verdana Italic">consectetur</emphasis> adipiscing elit.</para> 
<para sla:style="body">Integer egestas mi nec lectus molestie condimentum. Duis quam sapien, pharetra non tempor.</para></chapter>

Note that the script imports every text node including any whitespace and line ends. So paragraphs are imported as is in the xml file. The styles in the style attribute and the fonts in the override attribute should be available in the template otherwise the text is placed without setting a style or font.

Styles are inherited by default

In the previous example every para was labeled with a style-attribute. That is not necessary because styles are inherited by default.
The following example will give the same result:

<?xml version="1.0"?>
<chapter xmlns:sla="http://www.scribus.net/formatxml" sla:style="body"><title sla:style="header 1">The title of the chapter</title>
<para>Lorem ipsum dolor sit amet, <emphasis sla:override="Verdana Italic">consectetur</emphasis> adipiscing elit.</para> 
<para>Integer egestas mi nec lectus molestie condimentum. Duis quam sapien, pharetra non tempor.</para></chapter>

The para's don't have a style attribute. The style attribute is set to the chapter so the contents every descendant of chapter will be set to "body". However "body" is not applied to <title> because it has a style attribute of its own. So "header 1" will be applied to the contents of <title>. The para's do not have a style attribute so "body" is applied which inherited from <chapter>.

Source

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

try:
   # Please do not use 'from scribus import *' . If you must use a 'from import',
   # Do so _after_ the 'import scribus' and only import the names you need, such
   # as commonly used constants.
   import scribus
except ImportError, err:
   print "This Python script is written for the Scribus scripting interface."
   print "It can only be run from within Scribus."
   sys.exit(1)

import xml.sax
import xml.sax.handler

class XMLFormatter (xml.sax.handler.ContentHandler):
   """Formats xml in scribus document
   Every text node including whitespace is placed in the document
   Paragraph styles are applied through an attribute sla:style in the namespace "http://www.scribus.net/formatxml".
   Character styles are applied through the attribute sla:override in the same namespace
   Paragraph and character styles are inherited automatically"""

   def __init__ (self):
       self.styles = [None]
       self.overrides = [None]
       self.template = ""
       self.document = ""
       self.name = ""
       self.in_namespace = False
       self.prefix = None
       self.first = True

   def make_textframe (self):
       margins = scribus.getPageMargins()
       size = scribus.getPageSize()
       w = size[0] - margins[1] - margins[3]
       h = size[1] - margins[0] - margins[2]
       return scribus.createText (margins[1], margins[0], w, h)
       
   def startPrefixMapping(self, prefix, uri):
       """checks if current node is in proper namespace"""
       if uri == "http://www.scribus.net/formatxml":
           self.in_namespace = True
           if prefix != "":
               self.prefix = prefix + ":"
               
   def endPrefixMapping(self, prefix):
       """signal that we are no longer in namespace"""
       if self.in_namespace:
           self.in_namespace = False
           self.prefix = None
       
   def startDocument(self):
       """Open document"""
       scribus.openDoc(self.template)
       self.name = self.make_textframe()
       
   def add_style (self, stack, style):
       if style == None:
           style = stack[-1]
       stack.append(style)
               
   def startElementNS(self, name, qname, attrs):
       """record styles or overrides"""
       attrs_names = attrs.getQNames()
       if self.in_namespace:
           if self.prefix + "style" in attrs_names:
               style = attrs.getValueByQName(self.prefix + "style")
           else:
               style = None
           self.add_style(self.styles, style)
           if self.prefix + "override" in attrs_names:
               override = attrs.getValueByQName(self.prefix + "override")
           else:
               override = None
           self.add_style(self.overrides, override) 
       
   def endElementNS(self, name, qname):
       self.styles.pop()
       self.overrides.pop()
       
   def shouldSetStyle (self):
       if self.first:
           return True
       else:
           return scribus.getAllText(self.name)[-1] == "\r"
       
   def characters(self, content):
       """place text, apply style"""
       start = scribus.getTextLength(self.name)
       go_ahead = self.shouldSetStyle()
       scribus.insertText (content, -1, self.name)
       scribus.selectText(start, len(content), self.name)
       if self.styles[-1] != None and go_ahead:
           try:
               scribus.setStyle(self.styles[-1], self.name)
           except scribus.NotFoundError:
               scribus.createParagraphStyle(self.styles[-1])
               scribus.setStyle(self.styles[-1], self.name)
       if self.overrides[-1] != None:
           try:
               scribus.setFont(self.overrides[-1], self.name)
           except ValueError:
               pass
       self.first = False
       
   def flow(self):
       while(scribus.textOverflows(self.name) > 0 and scribus.getTextLines(self.name)):
           current = self.name
           scribus.newPage(-1)
           scribus.gotoPage( scribus.pageCount() )
           self.name = self.make_textframe()
           scribus.linkTextFrames(current, self.name)

   def endDocument(self):
       """Save and close document"""
       self.flow()
       scribus.saveDocAs(self.document)
       scribus.closeDoc()
#end class XMLFormatter    

class UserCanceled (Exception):
   pass
#end class UserCanceled

def format(xml_file, scribus_template, document):
   fmt = XMLFormatter ()
   fmt.template = scribus_template
   fmt.document = document
   p = xml.sax.make_parser()
   p.setFeature(xml.sax.handler.feature_namespaces, True)
   p.setContentHandler(fmt)
   p.parse(xml_file)

def getFile(caption, filter, defaultname, issave):
   file = scribus.fileDialog(caption, filter, defaultname, issave)
   if len(file) == 0:
       raise UserCanceled("canceled")
   else:
       return file

def main():
   """Formats xml file using Scribus template"""
   try:
       xml_file = getFile("XML File", 'XML (*.xml)')
       template = getFile("Scribus template", "Scribus document (*.sla)")
       document = getFile("Save document", "Alles (*.*)", xml_file.replace(".xml", ".sla"), False, True)
       format (xml_file, template, document)
   except UserCanceled:
       pass
   except xml.sax.SAXParseException:
       scribus.messageBox("XML Error", "XML Error: please check your document")
   
def main_wrapper():
   """The main_wrapper() function disables redrawing, sets a sensible generic
   status bar message, and optionally sets up the progress bar. It then runs
   the main() function. Once everything finishes it cleans up after the main()
   function, making sure everything is sane before the script terminates."""
   try:
       scribus.statusMessage("Running script...")
       scribus.progressReset()
       main()
   finally:
       # Exit neatly even if the script terminated with an exception,
       # so we leave the progress bar and status bar blank and make sure
       # drawing is enabled.
       if scribus.haveDoc():
           scribus.setRedraw(True)
       scribus.statusMessage("")
       scribus.progressReset()

# This code detects if the script is being run as a script, or imported as a module.
# It only runs main() if being run as a script. This permits you to import your script
# and control it manually for debugging.
if __name__ == '__main__':
   main_wrapper()