import os, re, traceback, logging
import glob
from lxml import etree
from time import time
from bl.dict import Dict
from bl.string import String
from bl.text import Text
from bxml.xml import XML
from bkgen import NS
from bkgen.source import Source
log = logging.getLogger(__name__)
[docs]class ICML(XML, Source):
"model for working with ICML files (also idPkg:Story xml)"
ROOT_TAG = "Document"
POINTS_PER_EM = 12
NS = Dict(**{
'idPkg': "http://ns.adobe.com/AdobeInDesign/idml/1.0/packaging"
})
[docs] def documents(self, path=None, **params):
"""return a list of documents containing the content of the document"""
fn = os.path.join(
path or os.path.dirname(os.path.abspath(self.fn)),
os.path.basename(os.path.splitext(self.fn)[0]+'.xml'))
return [self.document(fn=fn, **params)]
[docs] def document(self, fn=None, **params):
"""produce and return XML output from this ICML document.
fn = the output filename; default os.path.splitext(ICML.fn)[0] + '.xml'
"""
import bkgen.converters.icml_document
from .document import Document
x = self.transform(bkgen.converters.icml_document.transformer,
fn=fn or os.path.splitext(self.fn)[0]+'.xml',
DocClass=Document,
**params)
return x
[docs] def stylesheet(self, fn=None, points_per_em=None):
"""create a CSS stylesheet, using the style definitions in the ICML file."""
from bl.file import File
from bf.styles import Styles
if points_per_em is None: points_per_em = self.POINTS_PER_EM
styles = Styles()
for style in self.root.xpath("//CharacterStyle | //ParagraphStyle"):
clsname = self.classname(style.get('Name'))
if style.tag == 'CharacterStyle':
if clsname == 'No-character-style':
selector = 'a, span'
else:
selector = 'a.' + clsname + ', span.' + clsname
else:
if clsname == 'No-paragraph-style':
selector = 'p'
else:
selector = 'p.' + clsname
if len(self.root.xpath(
"//ParagraphStyleRange[@AppliedParagraphStyle='%s' and .//Table]"
% style.get('Self'))) > 0:
selector += ', div.' + clsname
styles[selector] = self.style_block(style, points_per_em=points_per_em)
# print(selector, styles[selector])
ss = Text(fn=fn or os.path.splitext(self.fn)[0]+'.css', text=Styles.render(styles))
return ss
[docs] @classmethod
def classname(C, stylename):
"""convert an Indesign style name into an HTML class name"""
name = stylename.replace('$ID/','').strip('/').replace('[','').replace(']','').replace(' ', '-')
name = String(name.split(':')[-1]).camelsplit().strip()
name = re.sub('[^0-9A-Za-z_\-]+', '-', name)
if re.search("^\d" ,name[0:1]) is not None:
name = '_' + name
return name
# query the style element for each supported attribute and build
# its value based on what is there. Work by CSS attributes rather
# than by ICML properties -- treat the style element as data to query
[docs] def style_block(self, elem, points_per_em=None):
"query style elem and return a style definition block"
points_per_em = points_per_em or self.POINTS_PER_EM
s = self.style_attributes(elem, points_per_em=points_per_em)
# inheritance -- unpack, more reliable than using @extend
based_on = elem.find('Properties/BasedOn')
if based_on is not None:
based_on_styles = elem.xpath("//%s[@Self='%s']" % (elem.tag, based_on.text))
if len(based_on_styles) > 0:
# recursive -- each style can override its base style, which is correct inheritance
bs = self.style_block(based_on_styles[0], points_per_em=points_per_em)
for k in bs.keys():
if k not in s.keys():
s[k] = bs[k]
return s
[docs] def include_mixin(self, style, mixin):
if style.get('@include') is None:
style['@include'] = ''
style['@include'] += ' ' + mixin
[docs] def style_attributes(self, elem, points_per_em=None):
"""query style elem for attributes and return a CSS style definition block.
"""
log.debug(elem.attrib)
points_per_em = points_per_em or self.POINTS_PER_EM
s = Dict()
# color
if elem.get('FillColor') is not None:
color = elem.get('FillColor').split('/')[-1]
if re.match("\d+\d+\d", color) is not None:
r,g,b = [c.split('=')[-1] for c in color.split(' ')]
s['color:'] = 'rgb(%s,%s,%s)' % (r,g,b)
elif color=='Black':
s['color:'] = 'rgb(0,0,0)'
elif color=='Paper':
s['color:'] = 'rgb(255,255,255)'
else:
s['color:'] = '"%s"' % color
# direction -- can't be included in epub
# if elem.get('CharacterDirection') == 'LeftToRightDirection':
# s['direction:'] = 'ltr'
# elif elem.get('CharacterDirection') == 'RightToLeftDirection':
# s['direction:'] = 'rtl'
# font-size
if elem.get('PointSize') is not None:
s['font-size:'] = "%.01fem" % round(float(elem.get('PointSize'))/points_per_em, 2)
# font-family
if elem.find('Properties/AppliedFont') is not None:
s['font-family:'] = '"%s"' % elem.find('Properties/AppliedFont').text
# font-style and font-weight
fs = elem.get('FontStyle')
if fs is not None:
fs = fs.lower()
if 'bold' in fs or 'black' in fs or 'heavy' in fs:
s['font-weight:'] = 'bold'
if 'italic' in fs or 'oblique' in fs:
s['font-style:'] = 'italic'
# font-variant
fv = []
if elem.get('Capitalization')=='SmallCaps':
fv.append('small-caps')
if len(fv) > 0:
s['font-variant:'] = ' '.join(fv)
# hyphens
# if elem.get('Hyphenation') == 'false':
# s['hyphens:'] = 'none'
# s['-webkit-hyphens:'] = 'none'
# letter-spacing
if elem.get('DesiredLetterSpacing') is not None:
n = int(elem.get('DesiredLetterSpacing'))
if n != 0:
s['letter-spacing:'] = "%d%%" % n
# margin-left
if elem.get('LeftIndent') is not None:
leftindent = float(elem.get('LeftIndent') or 0)/points_per_em
# firstindent = float(elem.get('FirstLineIndent') or 0)/points_per_em
# if firstindent < 0: # hanging indent
# val = "%.01fem" % round(leftindent + firstindent, 2)
# else: # regular indent
# val = "%.01fem" % round(leftindent, 2)
# s['margin-left:'] = val
s['margin-left:'] = "%.01fem" % round(leftindent, 2)
# margin-right
if elem.get('RightIndent') is not None:
val = "%.01fem" % round(float(elem.get('RightIndent'))/points_per_em, 2)
s['margin-right:'] = val
# margin-top
if elem.get('SpaceBefore') is not None:
val = "%.01fem" % round(float(elem.get('SpaceBefore'))/points_per_em, 2)
s['margin-top:'] = val
# margin-bottom
if elem.get('SpaceAfter') is not None:
val = "%.01fem" % round(float(elem.get('SpaceAfter'))/points_per_em, 2)
s['margin-bottom:'] = val
# text-indent
if elem.get('FirstLineIndent') is not None:
indent = float(elem.get('FirstLineIndent'))/points_per_em
val = "%.01fem" % round(indent, 2)
s['text-indent:'] = val
# page-break-before
if elem.get('StartParagraph') in ['NextColumn', 'NextFrame', 'NextPage']:
s['page-break-before:'] = 'always'
elif elem.get('StartParagraph') == 'NextOddPage':
s['page-break-before:'] = 'right'
elif elem.get('StartParagraph') == 'NextEvenPage':
s['page-break-before:'] = 'left'
elif elem.get('KeepWithPrevious') not in [None, 'false']:
s['page-break-before:'] = 'avoid'
# page-break-after
if elem.get('KeepWithNext') not in [None, 'false']:
s['page-break-after:'] = 'avoid'
# text-align
elem.get('Justification')
if elem.get('Justification') in ['LeftAlign', 'ToBindingSide']:
s['text-align:'] = 'left'
elif elem.get('Justification') == 'CenterAlign':
s['text-align:'] = 'center'
elif elem.get('Justification') in ['RightAlign', 'AwayFromBindingSide']:
s['text-align:'] = 'right'
elif elem.get('Justification') in ['LeftJustified', 'RightJustified', 'CenterJustified', 'FullyJustified']:
s['text-align:'] = 'justify'
# text-decoration (underline, strikethrough)
td = []
if elem.get('StrikeThru') == 'true':
td.append('line-through')
if elem.get('Underline') == 'true':
td.append('underline')
if len(td) > 0:
s['text-decoration:'] = ' '.join(td)
# text-transform
if elem.get('Capitalization')=='AllCaps':
s['text-transform:'] = 'uppercase'
# vertical-align
if elem.get('Position') in ['Superscript', 'OTSuperscript']:
s['vertical-align:'] = 'text-top'
s['font-size:'] = '70%'
elif elem.get('Position') in ['Subscript', 'OTSubscript']:
s['vertical-align:'] = 'bottom'
s['font-size:'] = '70%'
elif elem.get('CharacterAlignment') in ['AlignBaseline', 'AlignEmBottom', 'AlignICFBottom']:
s['vertical-align:'] = 'text-bottom'
elif elem.get('CharacterAlignment') in ['AlignEmCenter']:
s['vertical-align:'] = 'middle'
elif elem.get('CharacterAlignment') in ['AlignEmTop', 'AlignICFTop']:
s['vertical-align:'] = 'text-top'
# word-spacing
if elem.get('DesiredWordSpacing') is not None:
n = int(float(elem.get('DesiredWordSpacing'))) - 100
if n != 0:
s['word-spacing:'] = "%d%%" % n
log.debug("=> style: %r" % s)
return s