Skip to content

Repository files navigation

SimpleIDML

https://coveralls.io/repos/Starou/SimpleIDML/badge.pngSupported Python versionsLicenseTravis C.I.

Installation

Use pip:

pip install SimpleIDML

Or:

python setup.py build
sudo python setup.py install

Python support

  • Python 2: 2.7.x
  • Python 3: 3.5+

Any questions?

https://groups.google.com/forum/#!forum/simpleidml-users

Developers

vagrant up
vagrant ssh
cd tests
python runtests.py

A Python 3 virtualenv is activated at login but you can switch to Python 2.7:

source~/venv_py2/bin/activate

What is SimpleIDML?

SimpleIDML is a Python library to manipulate Adobe® InDesign® IDML file. The main purpose being the ability to compose IDML files together and produce complex documents from simple pieces and to separate the data from the structure.

The philosophy behind SimpleIDML is to keep separated the content and the structure and to use XML files to feed your documents by using the XML Structure in InDesign. Keeping this isolation is important to ease the debugging and to keep track of what is going on.

I urge you to take a look in the regressiontests directory for real-world examples.

Uses cases - success story(ies)

Le Figaro - FigaroClassifieds

SimpleIDML is used in production at Le Figaro aside in-house tools managing the content of Classifieds Ads magazines like Propriétés de France or Belles Maisons à louer. These tools produces XML files describing the page layout (which IDML templates and sub-templates to use) and the page content. The XML files feed another tool - the one using SimpleIDML - that compose the final page.

The steps of the (simplified) process of composition are:

  1. Get the main IDML template (the page) ;
  2. Ad the sub-templates (the ads) into the page template ;
  3. Import the content into the final IDML file ;
  4. Edit the file in InDesign ;
  5. Push the changesets back to the content management application and update the database.

There is a lot of cool features in this application. You can update a part of a page already or partially composed for example.

Architecture

These applications are web-applications. The communication is done by web-services feeding a task queue (RabbitMQ/Celery).

The performances are quite good. Composing a document require a fraction of a second.

What are IDML files?

IDML (InDesign Markup Language) files are a Zip archives (Adobe calls them packages) storing essentially XML files. Adobe made a descent job because those files can completely express the content of the native (binary) documents. This is a small revolution in the print world when it comes to automatically process files in both ways from templates and database (Round-trip) without using proprietary server-edition of Publishing Software.

What does SimpleIDML do?

Package exploration

You can discover the structure of your IDML files:

>>>fromsimple_idmlimportidml>>>my_idml_package=idml.IDMLPackage("/path/to/my_main_document.idml")
>>>my_idml_package.spreads
[u'Spreads/Spread_ub6.xml', u'Spreads/Spread_ubc.xml', u'Spreads/Spread_uc3.xml']
>>>my_idml_package.stories
[u'Stories/Story_u139.xml', u'Stories/Story_u11b.xml',
u'Stories/Story_u102.xml', u'Stories/Story_ue4.xml']

Some attributes are lxml.etree Elements or Documents:

>>>my_package.font_families
[<ElementFontFamilyat0x1010048c0>,
<ElementFontFamilyat0x101004a50>,
<ElementFontFamilyat0x101004aa0>,
<ElementFontFamilyat0x101004af0>]
>>> [e.get("Name") foreinmy_package.font_families]
['Minion Pro', 'Myriad Pro', 'Kozuka Mincho Pro', 'Vollkorn']
>>>my_package.xml_structure<ElementRootat0x101004910>>>>fromlxmlimportetree>>># print my_package.xml_structure_pretty() is a shortcut for:>>>printetree.tostring(my_package.xml_structure, pretty_print=True)
<RootSelf="di2"><articleXMLContent="u102"Self="di2i3"><StoryXMLContent="ue4"Self="di2i3i1"><titleSelf="di2i3i1i1"/><subtitleSelf="di2i3i1i2"/></Story><contentXMLContent="u11b"Self="di2i3i2"/><illustrationXMLContent="u135"Self="di2i3i3"/><descriptionXMLContent="u139"Self="di2i3i4"/></article><articleXMLContent="udb"Self="di2i4"/><articleXMLContent="udd"Self="di2i5"/><advertiseXMLContent="udf"Self="di2i6"/></Root>

xml_structure attribute is a representation of the XML Structure of your InDesign XML-ready document (The one you want to use to populate the content with data from an external XML file having the same structure).

Build package

There is a convenient script to create a IDML package from a flat directory called simpleidml_create_package_from_dir.py which should be in your PATH.

Compose document

Important: You should always use a with context when using side-effect methods on IDMLPackage instances returning new instances.

For example, the following is bad because my_doc initial instance reference is lost and the associated file cannot be properly closed. This may rise an exception on Windows platform if you try to os.unlink() an unclosed file.

fromsimple_idmlimportidmlmy_doc=idml.IDMLPackage("/path/to/my_main_document.idml")
my_doc=my_doc.prefix("main")

Instead, use:

fromsimple_idmlimportidmlmy_doc=idml.IDMLPackage("/path/to/my_main_document.idml")
withmy_doc.prefix("main") asf:
# some code.

Insert elements

Using the XML Structure you can ask SimpleIDML to insert into a document at a XML tag the content of another XML tag from another document. The tag paths are expressed using XPath syntax. Note that you should always make a copy of your idml files before altering them with shutil.copy2(src, dst) for instance and prefix your document before using insert_idml() to avoid reference collisions.

>>>fromsimple_idmlimportidml>>>idml_main=idml.IDMLPackage("/path/to/my_main_document.idml")
>>>idml_module=idml.IDMLPackage("/path/to/my_small_document.idml")
>>>withidml_main.prefix("main") asp_idml_main, \
>>>idml_module.prefix("article") asp_idml_article:
>>>withp_idml_main.insert_idml(p_idml_article, at="/Root/article[3]",
only="/Root/module[1]") asf:
>>>f.stories
['Stories/Story_article1u188.xml', 'Stories/Story_article1u19f.xml',
'Stories/Story_article1u1db.xml', 'Stories/Story_mainu102.xml',
'Stories/Story_mainu11b.xml', 'Stories/Story_mainu139.xml',
'Stories/Story_mainue4.xml']
>>>printf.xml_structure_pretty()
<RootSelf="maindi2"><articleXMLContent="mainu102"Self="maindi2i3"><StoryXMLContent="mainue4"Self="maindi2i3i1"><titleSelf="maindi2i3i1i1"/><subtitleSelf="maindi2i3i1i2"/></Story><contentXMLContent="mainu11b"Self="maindi2i3i2"/><illustrationXMLContent="mainu135"Self="maindi2i3i3"/><descriptionXMLContent="mainu139"Self="maindi2i3i4"/></article><articleXMLContent="mainudb"Self="maindi2i4"/><articleSelf="maindi2i5"><moduleXMLContent="article1u1db"Self="article1di3i12"><main_pictureXMLContent="article1u182"Self="article1di3i12i1"/><headlineXMLContent="article1u188"Self="article1di3i12i2"/><StoryXMLContent="article1u19f"Self="article1di3i12i3"><articleSelf="article1di3i12i3i2"/><informationsSelf="article1di3i12i3i1"/></Story></module></article><advertiseXMLContent="mainudf"Self="maindi2i6"/></Root>

Combine pages

You may need to gather pages from severals documents into a single one:

>>>edito_idml_file=IDMLPackage("magazineA-edito.idml")
>>>courrier_idml_file=IDMLPackage("magazineA-courrier-des-lecteurs.idml")
>>># Always start by prefixing packages to avoid collision.>>>withedito_idml_file.prefix("edito") asp_edito,\
>>>courrier_idml_file.prefix("courrier") asp_courrier:
>>>len(edito_idml_file.pages)
2>>>new_idml=p_edito.add_page_from_idml(p_courrier,
... page_number=1,
... at="/Root",
... only="/Root/page[1]")
>>>len(new_idml.pages)
3# The XML Structure has integrated the new file.>>>printetree.tostring(new_idml.xml_structure, pretty_print=True)
<RootSelf="editodi2"><pageSelf="editodi2ib"><articleSelf="editodi2ibif"><StoryXMLContent="editoue4"Self="editodi2ibifi1f"><titleSelf="editodi2ibifi1fi1"/><subtitleSelf="editodi2ibifi1fi2"/></Story><contentXMLContent="editou11b"Self="editodi2ibifi1e"/></article></page><pageSelf="editodi2i10"><advertiseXMLContent="editou1de"Self="editodi2i10i23"/></page><pageSelf="courrierdi2ib"><titleXMLContent="courrieru1b2"Self="courrierdi2ibi34"/><articleXMLContent="courrieru1c9"Self="courrierdi2ibi33"/><articleXMLContent="courrieru1e0"Self="courrierdi2ibi32"/><articleXMLContent="courrieru1fb"Self="courrierdi2ibi31"/><articleXMLContent="courrieru212"Self="courrierdi2ibi30"/></page></Root>

There is a convenient method to add several pages at once:

>>>edito_idml_file=IDMLPackage("magazineA-edito.idml")
>>>courrier_idml_file=IDMLPackage("magazineA-courrier-des-lecteurs.idml")
>>>bloc_notes_idml_file=IDMLPackage("magazineA-bloc-notes.idml")
>>>withedito_idml_file.prefix("edito") asp_edito,\
>>>courrier_idml_file.prefix("courrier") asp_courrier,\
>>>bloc_notes_idml_file.prefix("blocnotes") asp_bloc_notes:
>>>packages_to_add= [
... (p_courrier, 1, "/Root", "/Root/page[1]"),
... (p_bloc_notes, 1, "/Root", "/Root/page[1]"),
... ]
>>>new_idml=p_edito.add_pages_from_idml(packages_to_add)
>>>len(new_idml.pages)
4>>>new_idml.spreads
['Spreads/Spread_editoub6.xml',
'Spreads/Spread_editoubc.xml',
'Spreads/Spread_editoubd.xml']

Import/Export XML

Exporting as XML:

>>>idml_file=IDMLPackage("path/to/file.idml")
>>>printidml_file.export_xml()
<Root><module><main_picture/><headline>Helloworld!</headline><Story><article>Loremipsumdolorsitamet, ...</article><informations>Loremipsumdolorsitamet,</informations></Story></module></Root>

You can as well import XML file into your InDesign® documents. The following rules applies:

  • A node having the attribute simpleidml-setcontent="false" will not update the content of the corresponding element into the idml document (but its children will be updated).
  • A node having the attribute simpleidml-ignorecontent"true" will not update the content of the corresponding element into the idml document and its children.
  • In a ignorecontent context the content of a child node can be turned on with the simpleidml-forcecontent="true" flag.
  • Images references are passed by the href attribute. An empty value will remove the corresponding page items into the document.
  • Nested tag will be created if they are mapped with a character-style.
  • The style applied to the newly created tag is a combinaison of the parent character-styles and the mapped one.

Please take a look into the tests for in-depth examples.

Import PDF

A block can be used as a placeholder for a PDF file:

>>>withIDMLPackage("my_package.idml") asidml_file:
>>>withidml_file.import_pdf("file:/path/to/file.pdf", at="/Root/modules/module[2]") asf:
>>>f.export_xml()

Use InDesign server SOAP interface to convert a file

This require an InDesign Server and a directory that it can access in read/write. The same directory must be accessible by the client either by the filesystem or by FTP. The formats parameter is a list (of dicts) of formats you want your file to be exported into. The supported formats are jpeg, idml, pdf, indd and zip (this one returning a zipped InDesign package).

You can provide exports parameters using the params key. The list of supported parameters can be found with a simpleidml_indesign_save_as.py --help command.

Here some snippets:

fromsimple_idml.indesignimportindesignresponse=indesign.save_as("/path_to_file.idml", [{"fmt": "indd"}],
"http://url-to-indesign-server:port",
"/path/to/client/workdir",
"/path/to/indesign-server/workdir")[0]
withopen("my_file.indd", "w+") asf:
f.write(response)
response=indesign.save_as("/path_to_file.indd", [{"fmt": "idml"}],
"http://url-to-indesign-server:port",
"/path/to/client/workdir",
"/path/to/indesign-server/workdir")[0]
withopen("my_file.idml", "w+") asf:
f.write(response)
response=indesign.save_as("/path_to_file.indd", [{
"fmt": "pdf",
"params": {"colorSpace": "CMYK"},
}],
"http://url-to-indesign-server:port",
"/path/to/client/workdir",
"/path/to/indesign-server/workdir")[0]
withopen("my_file.pdf", "w+") asf:
f.write(response)

The response is a list of string because you can pass a list of formats and so generate several exports in a row (if performances matter):

fromsimple_idml.indesignimportindesignpdf_response, jpeg_response, zip_response=indesign.save_as(
"/path_to_file.indd",
[{"fmt": "pdf"}, {"fmt": "jpeg"}, {"fmt": "zip"}],
"http://url-to-indesign-server:port",
"/path/to/client/workdir",
"/path/to/indesign-server/workdir")

If the InDesign Server instance runs on a Windows machine, set the indesign_server_path_style parameter to "windows".

If the client access to the working directory via FTP, you must specify that in the ftp_params parameter:

{
'auth': ("ftp://ftp.foo.org", "user_account", "s3cret-pa55word"),
'passive': False,
'keepalive': True, # False by default (optional)'keepalive_interval': 30, # set socket.TCP_KEEPINTVL (optional)'keepalive_idle': 45, # set socket.TCP_KEEPIDLE (optional)'polite': False, # Unilaterally close ftp connection (optional)
}

A script (simpleidml_indesign_save_as) that wraps that function should be installed in your PATH.

Revisions

1.0.0

New features

  • Added support for Python 3

Backward incompatibilities

  • Removed support for Python 2.6

0.92.9

New features

  • Added simpleidml_indesign_profiles.py script to list the available joboptions files on the InDesign Server using the SOAP interface.

Bug fixes

  • Fix working directory cleaning of the SOAP server when an exception is raised. indesign.save_as() may be backward incompatible since the returned list may contains some None (instead of raising an exception before returning anything).
  • Give the list of available profiles (joboptions files) on the InDesign Server if the given 'pdfExportPresetName' is not found.

Backward incompatibilities

  • indesign.close_all_documents() has been replace the CloseAllDocuments class and its .execute() method.
  • Some util functions that wrap the basic file manipulations to manage the case of a ftp access to those files have been moved from indesign.py to a new ftp.py module.

0.92.8

New features

  • Added IDMLPackage.import_pdf() method.

Bug fixes

  • Fix bleedMarks in export.jsx.

0.92.7

Bug fixes

  • FillTint wasn't managed.
  • Force lxml < 4 in dependencies.

0.92.6

Bug fixes

  • Catch errors when InDesign SOAP server fails to complete a task and raise an exception.

0.92.5

Bug fixes

  • Handle <PDF> in IDMLPackage._get_item_translation_for_insert()

0.92.4

Bug fixes

  • Fix issue #11: Parent CharacterStyle not applied in import_xml() in some cases.

0.92.2

New features

  • More ftp parameters for indesign.save_as() function. Hardcoded socket parameters are now modifiable. And you can set the flag polite to False if you encounter hanging problem on ftp.quit() as I do. Being unpolite calls an unilateral and rude ftp.close(). Please upgrade your code with explicite values if you rely on the previous default behavior.

0.92.1

Bug fixes

  • indesign.save_as() uses a dedicated temporary working directory to avoid concurrent access on files.
  • Added a logger to indesign.save_as() ('simpleidml.indesign') and some debug messages.
  • Fixed hanging ftp.retrbinary() in indesign.save_as() calls by tuning the socket.

0.91.8

New features

  • Added support for PDF export presets in indesign.save_as().

0.91.7

New features

  • Added IMDLPackage.merge_layers(with_name) (Refs#7).
  • Added a new script simpleidml_indesign_close_all_documents.py.

Bug fixes

  • In IDMLPackage.insert_idml(), Elements from the same layer (but not tagged in the structure) are now added in the Spread of the document of destination.
  • Better support for Windows platform.
  • Fixed character style mapping with tag when using insert_idml.
  • Fixed Export XML in some edge case.
  • Added parameters to simpleidml_indesign_save_as when exporting to PDF.

Backward incompatibilities

  • indesign.save_as() formats parameters is now a list of dictionaries.

0.91.6

New features

  • Add the simpleidml-ignorecontent and simpleidml-forcecontent tags (XML attributes) allowing one to carefully exclude a node and its children during the import XML process.
  • indesign.save_as() now works with a client working directory over a FTP. This require wget to be on your system if you want to create zip packages.

Backward incompatibilities

  • indesign.save_as() require both a client workdir and a server workdir parameter.

0.91.5.5

Bugfixes

  • <EPS> elements in Spread weren't handled correctly.
  • All spread elements were added in the destination package when using insert_idml().

0.91.3

New features

Add a SOAP client to call a InDesign server to get INDD file and export in various formats.

0.91.2

New features

  • Ticket #20 - Suffix layers.

Backward incompatibilities

  • Ticket #22 - IDMLPackage.import_xml() parameter is a XML string and not a file object.

Bugfixes

Tickets #19, #21 (orphan layers), #23 (AssertXMLEqual), #24 (import_xml() failure).

About

Manipulate Adobe® InDesign® IDML files

Resources

Stars

4 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages