How to support this blog?

To support this blog, you can hire me as an OmegaT consultant/trainer, or you can send translation and project management jobs my way.

Search the site:

Showing posts with label xml. Show all posts
Showing posts with label xml. Show all posts

Maxprograms is back, take 2.

Free software by a world class XLIFF expert

I first wrote about Maxprograms in 2007. That was after Rodolfo M. Raya left Heartsome to pursue his activity as the Maxprograms software company. I’ve been meaning to write about his work for a long time now, especially since he decided to put all his products on Github, thus allowing translators to get the code, build the things, get the updates and run them on their side, for free.

Maxprograms also offers a lot of technical articles on the subjects of DITA, XLIFF and XML in localization. Check the page here if you are interested.

Rodolfo M. Raya is an editor of the XLIFF 2.0 specification and is a member of the OASIS XLIFF technical committee. He is also co-author of A Practical Guide to XLIFF 2.0.

April 15, 2023 update: Rodolfo has updated the build requirements.

  • JDK 17 or newer is required for compiling and building
  • Apache Ant 1.10.12 or newer
  • Node.js 18.15.0 LTS or newer
  • TypeScript 5.0.0 or newer
I've updated the version numbers below. Make sure you check the various requirements on the respective repository pages.

July 4, 2021 update: all of Maxprograms products are now available under an OSI approved Open Source license

Most of Maxprograms' products come with an easy-to-use installer and only the packages listed in italics below do not, because in their case an installer is not relevant.

If you check the repository list at https://github.com/rmraya?tab=repositories you will see the following software packages (among others). I added the product page on Maxprograms' site (the "..." links) to make it easier for you to find extra information:

  • Eclipse Public License 1.0 (OSI approved)
    • Fluenta (DITA translation manager) ...
    • Swordfish (standards based XLIFF CAT, supports Trados Studio packages) ...
    • TMXEditor (TMX editor, as the name suggests) ...
    • Stingray (document aligner) ...
    • XLIFFManager (GUI for OpenXLIFF) ...
    • OpenXLIFF (command line utility to create, merge, validate XLIFF files) ...
    • TMXValidator (validator/cleaner for TMX files) ...
    • TMEngine (translation memory engine) ...
    • SRXEditor (SRX editor) ...
    • MVDServer (simple web server) ...
    • XLIFFValidation (web-based XLIFF validation tool) ...
    • RemoteTM (remote TM server) ...
  • MIT (FSF and OSI approved)
    • Conversa (DITA publisher) ...
    • KeysAnalyzer (DITA keys analyzer) ...

A "normal" freelance translator would use Swordfish as an XLIFF translation editor/project manager. With eventually TMXEditor to work on client TMX, Stingray to align file sets, and TMXValidator to check and clean TMX files.

More advanced users, or PMs would use XLIFFManager or OpenXLIFF, along with XLIFFValidation and SRXEditor.

DITA specialists would use Fluenta, Conversa and KeyzAnalyzers.

Users who want to self-host services would use XLIFFValidation, MVDServer, RemoteTM.

Maxprograms' packages offer pretty much anything a translator would need, in very cleanly packaged solutions, with excellent user support, either community-based (on groups.io), or directly from Maxprograms if you are a subscriber.

And if your main translation editor in not Swordfish, you can still benefit from all the other packages depending on your needs.

DIY software

Fluenta, Swordfish, TMXEditor and Stingray are subscription-based, but allow translators to run the software for free from the source code (RemoteTM is proposed as a "software as a service" hosted by Maxprograms, but you can host it on your own server if you want).

What that means is that the packaged installer comes as a subscription, but if you don’t mind using Terminal to run your software, which is, let’s be honest, not much of a hurdle, you can use the software, access its updates, eventually make modifications for your own use, etc. for free.

The requirements to download, build and run the software are all given on each product’s Github repository, but here is the gist:

Ant, Node.js and Typescript are also available from Homebrew (https://brew.sh).

To build the software you’ll need to checkout (clone) the repositories. You also need to make sure that Ant uses Java 17 to build the package.

Rodolfo gives all the steps for building (for ex. Stingray):

    $ git clone https://github.com/rmraya/Stingray.git
    $ cd Stingray
    $ ant
    $ npm install
    $ npm start

Java ?

Maxprograms' applications used to be multiplatform Java only applications. Now they use a combination of Java and Javascript (even if the names are close, the two languages are totally unrelated) see below for Rodolfo's explanations.

Apple used to ship Java when macOS was young. It was in fact one of its selling points. Apple has stopped distributing Java a long time ago and now suggests that users install Oracle Java. Changes in the Oracle license and the availability of free Java distributions have allowed users to still benefit from Java without having to pay for a commercial user license.

Even after installing Adoptium's JDK, your machine will still prefer to use an older Java version if you have one installed on your machine.

Which means that if you run:

$ echo $JAVA_HOME

in Terminal and it won’t point to Java 17, you won’t be able to build the software.

On my machine, the above command gives

/Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home

A short investigation shows that the newly installed Java 17 is here:

/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/

The way to have ant use Java 17 instead of Java 11 for the build process is simply to give ant the right pointer:

$ export JAVA_HOME=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/; ant

Once you’re done with the process, you can export your Java_Home back to its original value, just to be on the safe side. You’ll need to point to Java 17 only to build the software after a code update ($ git pull).

Back to the command line

Once you’ve run

$ npm install

you’re free to launch the application any time you want with

$ npm start

That’s it. This procedure works for Swordfish, Stingray, TMXEditor.

You can also create an Applescript that packages the whole process into a nice application that you can use to launch the various packages as you'd launch standard macOS applications.

A simple Applescript to do that could be:

use AppleScript version "2.4" -- Yosemite (10.10) or later use scripting additions set SwordfishPath to "/path/to/Swordfish/local/repository" set myCommand to "cd " & SwordfishPath & "; npm start" tell application "Terminal"'s front window delay 0.1 do script myCommand in its last tab end tell

Electron ?

Maxprograms' applications used to be Java only applications. But Oracle (Java's main caretaker) neglect of GUI libraries led to a not very satisfactory situation for Java GUI application developers. Here is Rodolfo's rationale for his move away from Java-based GUI libraries:

I use HTML + JavaScript + CSS for the UI and Java for core libraries. In essence, my apps are web pages running on a Java server.

NodeJS provides access to Chrome's V8 JavaScript Engine. Electron is a bridge that provides native windowing environment for JavaScript pages displayed on top of Chrome.

JavaScript is a powerful language, but it is dangerously sloppy. Instead of coding directly on JavaScript, I use TypeScript (which is a safer language) and "transpile" my code to JavaScript for deployment.

I have not switched completely to TypeScript because there aren't good XML libraries for JavaScript. All translation standards are defined using XML and Java is still the best option for handling them. The good news is that JLIFF (a JSON-based version of XLIFF) is currently in development.

Why switch to HTML + CSS for the UI if Java is a cross-platform tool?, you may ask. The answer is simple: Java dropped its major UI libraries.

Initially, Java shipped with AWT, a horrible toolkit with poor design and feature lacking. AWT was so bad that two options replaced it: Swing (originally developed by Sun and currently used by OmegaT) and SWT (the one I used in Swordfish, developed by Eclipse Foundation).

Oracle deprecated Swing in Java 8 and replaced it with JavaFX. When Java 9 was released, JavaFX was abandoned and development moved to its own open-source project, away from Oracle.

The Eclipse Foundation, which developed and maintained SWT for years, stopped improving SWT when JavaFX appeared. It did not fully embrace JavaFX and SWT is languishing since then.

With SWT dying, I looked at JavaFX. It tries to mimic HTML + CSS without success. Not good enough. The real alternative: adopt HTML and CSS.

Et voilà!

I'm not even scratching the surface of what Maxprograms has offered to translators since Heartsome disappeared. Each software package deserves a whole article.

Maxprograms' packages are yet another proof that working on macOS as translators, project managers or localization engineers is possible. And we're lucky that most of the multiplatform packages that allow us to work on macOS are backed by very talented people and communities who do their best to keep us from having to change of platform.

Disclaimer: I am not affiliated in any way to Maxprograms. Just so that you know...

OmegaT and the Okapi Framework plugin for OmegaT

Introducing the Okapi Framework plugin for OmegaT


This is a summary for people who want a "shortcut".

[Updated Feb. 1st 2022 with link and version updates]

The Okapi Framework is "a cross-platform and free open-source set of components and applications that offer extensive support for localizing and translating documentation and software."


From very early on the people behind the Okapi Framework have helped OmegaT users with a number of facilities, among which the "Filters Plugin for OmegaT", which brings transparent support for file formats such as:

  • Doxygen-commented files
  • HTML files
  • InDesign IDML files
  • JSON files
  • Markdown projects.
  • Transifex projects.
  • TS files
  • Trados TagEditor TTX files
  • Wordfast pro TXML files
  • XLIFF 1.2 files
  • XLIFF 2.x files
  • XML files (with or without ITS support)
  • YAML files

Basic requirement

Make sure you have a recent version of OmegaT (use version 4 and above).

To use the plugin in OmegaT

  1. Enter your OmegaT configuration folder by using the Options > Access Configuration Folder menu in OmegaT
  2. If there is no "plugins" folder in that configuration folder, create one
    Create a "plugins" folder in the OmegaT configuration folder if it is not already there.
    Create the "plugins" folder if it is not already there.
  3. Download the plugin from the distribution site (1.9-1.41 at the time of this writing): https://okapiframework.org/wiki/index.php?title=Okapi_Filters_Plugin_for_OmegaT#Download_and_Installation 
  4. Unzip if necessary, check the readme.html file if you want
  5. Copy the okapiFiltersForOmegaT-1.9-1.41.jar file to the OmegaT plugins folder that you identified above
  6. Restart OmegaT
Once you have installed the plugin, open OmegaT's File Filters preferences (Options > File Filters...) You should see a number of filters that have their name appended with "(Okapi)".

The Okapi plugin provides a number of additional filters that are identified in the OmegaT interface by the string "(Okapi)" appended to their names
The additional filters provided by the Okapi plugin

Et voilà.

Now, 3 things

  1. [obvious, but it doesn't cost much to repeat]
    If anything doesn't work as planned, carefully read the information in the links above and see that you have followed all the instructions and checked all the requirements. This article is only a summary.
  2. [beginner level]
    OmegaT already has native filters for some of the Okapi filters. If you want OmegaT to use the Okapi filters, uncheck the native filters.
  3. [advanced level]
    The Okapi filters have a default behavior that is reasonable enough. If you want to modify that behavior, you'll need to create a custom filter parameters file (
    .fprm extension) from within Rainbow. You'll use OmegaT's filter specific options (select the Okapi filter in OmegaT's Filter Filters interface, click on "Options...") to select that file so that OmegaT uses it.

    It is possible to customize the Okapi filters behavior by adding parameters files created in Okapi's Rainbow
    Adding a parameter file to customize the Okapi XLIFF filter behavior

Simple XML creation with AppleScriptObjectiveC

(*
This article is an attempt at putting together a practical introduction to using AppleScriptObjectiveC from all the information I gathered when creating a stand-alone application that required XML generation. Including the code, it is 3000 words. It's trivial for most of you, but it took me quite some time to put all the parts together and then to document them. Everything is still not super clear, so go ahead and tear it apart. :) I first give the code without any comments and then I add block comments to explain what needs explanations.

The code generates an omegat.project file that is equivalent to what OmegaT generates when creating a new project. I'm using that generation code in a bigger script that creates full fledged OmegaT projects without going through the OmegaT interface. The code can be applied to any kind of generic XML generation with a few tweaks. I use something similar in an Excel to TMX conversion script as well. I'll eventually publish both here.
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

on CreateOmegaTProjectFile(ProjectSettings)
set project_tags to {"source_dir", "source_dir_excludes", "target_dir", "tm_dir", "glossary_dir", "glossary_file", "dictionary_dir", "source_lang", "target_lang", "source_tok", "target_tok", "sentence_seg", "support_default_translations", "remove_tags", "external_command"}
set masks to {"**/.svn/**", "**/CVS/**", "**/.cvs/**", "**/desktop.ini", "**/Thumbs.db", "**/.DS_Store"}
set valueindex to 0
set projectRoot to current application's NSXMLNode's elementWithName:"omegat"
set theProject to current application's NSXMLNode's documentWithRootElement:projectRoot
theProject's setCharacterEncoding:"UTF-8"
theProject's setStandalone:true
set project to current application's NSXMLNode's elementWithName:"project"
set projectVersion to current application's NSXMLNode's attributeWithName:"version" stringValue:"1.0"
project's addAttribute:projectVersion
projectRoot's addChild:project
repeat with child in project_tags
set valueindex to valueindex + 1
if contents of child is not "source_dir_excludes" then
set child to (current application's NSXMLNode's elementWithName:child stringValue:(item valueindex of ProjectSettings))
(project's addChild:child)
else
set child to (current application's NSXMLNode's elementWithName:"source_dir_excludes")
(project's addChild:child)
set source_dir_excludes to (project's elementsForName:"source_dir_excludes")'s firstObject()
repeat with mask in masks
set mask to (current application's NSXMLNode's elementWithName:"mask" stringValue:mask)
(source_dir_excludes's addChild:mask)
end repeat
end if
end repeat
set theData to theProject's XMLDataWithOptions:((get current application's NSXMLNodePrettyPrint) + (get current application's NSXMLDocumentTidyXML))
theData's writeToFile:(item 16 of ParametersList) options:(current application's NSDataWritingAtomic) |error|:(missing value)
end CreateOmegaTProjectFile


set ProjectSettings to {"PATH_1", "MASKS", "PATH_2", "PATH_3", "PATH_4", "FILE_5", "PATH_6", "LANG_7", "LANG_8", "TOKENIZER_9", "TOKENIZER_10", "SEGMENTATION_11", "DEFAULT_TRANSLATION_12", "REMOVE_TAGS_13", "EXTERNAL_COMMAND_14", ((POSIX path of (path to desktop folder)) & "omegat.project")}

my CreateOmegaTProjectFile(ProjectSettings)


(*
There are a number of solutions in AppleScript to create XML but nothing out of the box to generate generic data. System Events cannot create generic XML, it can only modify existing files. The only format it can natively produce is the "property list" format, used to store application preferences, etc. There are solutions that involve concatenating strings and doing a lot of checks on the data to make sure the output is valid (in XML a number of characters are forbidden, so all strings that are concatenated need to be thoroughly checked for those) but they are neither elegant nor robust. There are libraries available, but they do a lot more than what casual users need.

I thought that offering a simple solution for a simple problem that would provide the reader with a step-by-step introduction to reading the Foundation documentation and understanding how to use it with vanilla AppleScript was a better approach, at least for me, since short of having scriptable applications that do what you want, the only way to do really complex things in AppleScript is to use Foundation.

My problem, which is common to all amateur AppleScript users, is a problem of discoverability and of fluency. AppleScript is not a trivial language, a systematic description of its features is non-existent, and when you want to go beyond the "tell Finder to sort the mess on my Desktop" you end up needing to check a lot of references that either consider that you know a lot, or that you don't know much. There is no middle ground and no easy way to find what you need without actually asking. In fact, I still wonder how I could have written this code without the help of people who just seem to "know". There is no clear discoverability path to the information I gathered here.
AppleScriptObjC is not described in the AppleScript Language Guide issued by Apple. The 3rd edition of "Learn AppleScript" by Hamish Sanderson and Hanaan Rosenthal from Apress (2010) discusses some aspects of AppleScriptObjC but the document that best describes it is "Everyday AppleScriptObjC" by Shane Stanley (2015). The Foundation framework, along with other frameworks that can be accessed by AppleScriptObjC are described in the developer documentation (either in Xcode or online) with Objective-C use in mind (syntax, etc.) so we'll use the 3 references to go through the code.

Becoming fluent in a language is a problem both for the linguist and for the programmer. Fluency only comes with regular practice based on sound references, and regular contact with "natives". As far as AppleScript (and AppleScriptObjC) is concerned, "natives" can be found in a number of very interesting places on the internet. The first is the official AppleScript User List (ASUL) hosted by Apple. Apple has been a bad host in recent years so fear of losing this resource has motivated some of its users to create an independent list, hosted on groups.io. There also is the Macscripter web forum. I'm not super fond of web forums. Their user interface is clumsy more often than not and this one is no exception. But it's been around for ever and a number of world-class experts are there to comment on code and generally manage the community. Then, there is the ScriptDebugger's user forum. Conversations there generally revolve around higher level topics, the web forum is a modern one and the user experience is of extremely high quality.

Links:

Ok, so, you're all set, no more chatting, let's check the code.
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

(*
As seen in the script headers above, we'll be using the "Foundation" framework which allows us to directly work on the XML tools that macOS offers. Without this declaration we can't access what we need.

This code will generate an XML file that will reproduce the following typical omegat.project file, with default values for the project folders, English as source language, Japanese as target language, sentence segmentation and default translations enabled, and no external command to process when creating the target files.

In XML, the order of tags as well as the white space used to present them is irrelevant, so the appearance of the data will change depending on how the code was produced. It may not be exactly like a "genuine" OmegaT file, but as far as the meaning of the data and OmegaT proper are concerned that is not relevant.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<omegat>
    <project version="1.0">
        <source_dir>__DEFAULT__</source_dir>
        <source_dir_excludes>
            <mask>**/.svn/**</mask>
            <mask>**/CVS/**</mask>
            <mask>**/.cvs/**</mask>
            <mask>**/desktop.ini</mask>
            <mask>**/Thumbs.db</mask>
            <mask>**/.DS_Store</mask>
        </source_dir_excludes>
        <target_dir>__DEFAULT__</target_dir>
        <tm_dir>__DEFAULT__</tm_dir>
        <glossary_dir>__DEFAULT__</glossary_dir>
        <glossary_file>glossary.txt</glossary_file>
        <dictionary_dir>__DEFAULT__</dictionary_dir>
        <source_lang>EN</source_lang>
        <target_lang>JA</target_lang>
        <source_tok>org.omegat.tokenizer.LuceneEnglishTokenizer</source_tok>
        <target_tok>org.omegat.tokenizer.LuceneJapaneseTokenizer</target_tok>
        <sentence_seg>true</sentence_seg>
        <support_default_translations>true</support_default_translations>
        <remove_tags>true</remove_tags>
        <external_command></external_command>
    </project>
</omegat>

The tokenizers are generally not set by the user because OmegaT would automatically select them based on the corresponding language codes.
Anything else is user settable, even the "excludes" files, even though that is an advanced setting that most users should not have to consider.

So, we're going to create a handler that takes as input the following values:

• source_dir_value: path to directory
• source_dir_excludes_masks: list of mask tags
• target_dir_value: path to directory
• tm_dir_value: path to directory
• glossary_dir_value: path to directory
• glossary_file_value:  path to text file
• dictionary_dir_value: path to directory
• source_lang_value: string language code (we do need to check the validity of the code based on the appropriate ISO standard, but we consider here that the check has been made upstream, if only to keep the XML generation code free of such external checks)
• target_lang_value: string language code (see above)
• source_tok_value: string automatically proposed by the script, based on language code (here again, the tokenizers corresponding to the source language should be a valid tokenizer but we let the upstream code select and validate it to stick to the core XML generation code)
• target_tok_value: string automatically proposed by the script, based on language code (see above)
• sentence_seg_value: boolean
• support_default_translations_value: boolean
• remove_tags_value: boolean
• external_command_value: string to be sent to exec when the target files are created, can be empty if no action is requested.

We'll make this code a handler so that we can call it without having to copy it every time we need it. This is better because it allows to logically separate portions of the code.
*)

on CreateOmegaTProjectFile(ProjectSettings)
(*
The first few lines below are necessary to initialize the XML tags. First comes "project_tags", a list of tags that are used in the file. There are 15 tags, of which 14 tags only take a simple value, either a path, or a text string, etc. As we can see above, "source_dir_excludes" takes a list of "masks" tags which we'll create with "masks", which contains the patterns that describe the type of files to be ignored by OmegaT. Last, we initialize "valueindex", an index that will be used to associate the tag to its value found in the list that we feed the handler.
  Then comes the beginning of the XML structure creation.
*)
set project_tags to {"source_dir", "source_dir_excludes", "target_dir", "tm_dir", "glossary_dir", "glossary_file", "dictionary_dir", "source_lang", "target_lang", "source_tok", "target_tok", "sentence_seg", "support_default_translations", "remove_tags", "external_command"}
set masks to {"**/.svn/**", "**/CVS/**", "**/.cvs/**", "**/desktop.ini", "**/Thumbs.db", "**/.DS_Store"}
set valueindex to 0
(*
We're first going to create an XML element that we'll then use as the XML document root. We'll create the other elements once that is done.
*)
set projectRoot to current application's NSXMLNode's elementWithName:"omegat"
(*
This is our first line of AppleScriptObjC. First question: how do we call the Foundation items that we need to work with?
"Everyday AppleScriptObjC":
"Class names are effectively properties of the current application (which is, in turn, the parent of AppleScript in your script)."

"Learn Applesccript":
"AppleScriptObjC presents Cocoa classes as class elements of the current application object."

So, everything we'll call from Cocoa will be called from "current application", hence the use of "current application's NSXMLNode".

In the Xcode documentation, NSXMLNode is described as, well, an XML node.
 
As the documentation says, an XML node can be anything from: an element, an attribute, text, a processing instruction, a namespace, or a comment.
 
Here we use the "elementWithName" method to create the element. Clicking on its description in the Xcode documentation browser shows that the method is a "type method" and returns "an NSXMLElement object with a given tag identifier, or name".
 
"Type methods" apply to classes, they are generally used to create objects which are specific instances of a given class. When we'll work on the objects themselves we'll need "instance methods". In the documentation, "type methods" have a "+" prefixed to their name and "instance methods" have a "-" prefixed instead.
 
The syntax for such method calls is methodName:parameter, so here we have: elementWithName:"omegat"  This syntax will be used for all the other elements that we create.
 
It is important to note that we are using a shortcut here. As Shane wrote on ASUL:
"(elementWithName is) what's known as a convenience method. You're actually making a particular subclass of NSXMLNode, an NSXMLElement.
You could have used:
set projectRoot to current application's NSXMLElement's alloc()'s initWithName:"omegat"
But convenience methods are common, if not following any particular logic of when and where they're provided. Something like "stringWithString:" instead of "alloc()'s initWithString:" is a common example."
 
To make sense of that we need to know how Objective-C/Cocoa works:
"Everyday AppleScriptObjC":
"The equivalent (of AppleScript's make) in Cocoa is actually a two-stage process: first the object is created by allocating memory for it, and then it is initialized.  The first stage is done using the +alloc method. You will not see it listed (in the documentation) because it is a method all classes inherit from the NSObject class."
If we check the NSObject description, we see "+ alloc" which is described as "Returns a new instance of the receiving class" and later "You must use an init... method to complete the initialization process." Then we see "- init", which is described as "Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated." In the method description we also see that it only exists for Objective-C and not for Swift which only has "init()" to cover allocation and initialization in one fell swoop.
So, instead of using that longer process, we prefer to use that "convenience method" and make the code slightly shorter.
Foundation reference:
*)
set theProject to current application's NSXMLNode's documentWithRootElement:projectRoot
(*
Here again, we'll use a convenience method that does not require us to go through the alloc-init process. Now we have a XML document and its root element. We'll need a few more things to make our output look like what OmegaT needs.
Foundation reference:
*)
theProject's setCharacterEncoding:"UTF-8"
(*
"setCharacterEncoding" is not documented in the Foundation documentation. The closest thing related to an NSXMLDocument that we have is "characterEncoding" (notice the case for "Character": lower case in the documentation, upper case here), which is described as an "instance property", which seems to correspond since theProject is indeed an instance of NSXMLDocument.
The "set" prefix is explained in "Everyday AppleScriptObjC":
"To set a new value for a Cocoa property, assuming it is not read-only, you use the word set, followed by the property name with the  first letter in uppercase, followed by a colon or underscore and parentheses, depending on the syntax.  The single argument is then the proposed new value."
Now we understand why "set" is prefixed and why characterEncoding is changed to CharacterEncoding when prefixed with set. If we need to get the characterEncoding property of the document, let's not forget about the letter case...
Setting characterEncoding happens to be enough to create the <?xml version="1.0" encoding="UTF-8"?> line in our example and to actually encode the data in UTF-8.
Foundation reference:
*)
theProject's setStandalone:true
(*
setStandalone works like setCharacterEncoding and adds the standalone="yes" part to our xml declaration.
I had the following line in a previous version of this code:
theProject's setDocumentContentKind:(current application's NSXMLDocumentXMLKind)

but we can dispense with it since XML is the default kind of XML document (other kinds include HTML, XHTML and text). Still there is something in this line that we ought to remember for other occasions. "documentContentKind" is an instance property, like "standalone". To set it we must thus use "setDocumentContentKind". The possible values for a documentContentKind are documented as "enumerations", of which NSXMLDocumentXMLKind is the default value in the case of an XML document. To use NSXMLDocumentXMLKind as a value, we must do as we've done for other Cocoa items: call them from the current application, hence the (current application's NSXMLDocumentXMLKind).

Foundation reference:
*)
set project to current application's NSXMLNode's elementWithName:"project"
set projectVersion to current application's NSXMLNode's attributeWithName:"version" stringValue:"1.0"
project's addAttribute:projectVersion
projectRoot's addChild:project
(*
We're still in NSXMLNode territory here. Now we're creating the "project" element with a "version" attribute that has a "1.0" value.
To do this, we first create the element and we then separately create an attribute node by using the "attributeWithName:stringValue:" method (see the Xcode description) that actually comes in two parts: the attributeWithName part and the stringValue part.

Once created, the 2 nodes have no relation to each other or to anything we've created so far. We need to "link" everything together now and we do that with the 2 lines that follow:
"addAttribute" is documented as an instance method, which is good because "project" is an instance of "NSXMLNode" and "adds an attribute node to the receiver", which is exactly what we're trying to do. The parameter is "projectVersion", the attribute node that we created 1 line before that.

Now we have an element that looks like <project version="1.0"></project> and we need to add it as a child of the document root element. That's what "addChild" does: an instance method that "adds a child node after the last of the receiver’s existing children". The receiver is projectRoot and the child is project.
Foundation reference:
*)
repeat with child in project_tags
set valueindex to valueindex + 1
if contents of child is not "source_dir_excludes" then
set child to (current application's NSXMLNode's elementWithName:child stringValue:(item valueindex of ProjectSettings))
(project's addChild:child)
else
set child to (current application's NSXMLNode's elementWithName:"source_dir_excludes")
(project's addChild:child)
set source_dir_excludes to (project's elementsForName:"source_dir_excludes")'s firstObject()
repeat with mask in masks
set mask to (current application's NSXMLNode's elementWithName:"mask" stringValue:mask)
(source_dir_excludes's addChild:mask)
end repeat
end if
end repeat
(*
Now that we've been through the basics of using Foundation to create a basic XML structure, the rest of the code is very straightforward. We're running a loop on the tag list that was created at the beginning of the script and for each "child" we'll set a "stringValue" that corresponds to the item at the same position in the ProjectSettings that has been sent to this handler.
Once the element and its string value are created, we add the whole as a child to the "project" element that we created just above.
The only exception to that process is when we bump into "source_dir_excludes". Here, what happens is that we do not use stringValue  to set the element value since it is going to be made of a list of <mask> tags. Instead of that, we first create the tags and add them one by one as children to "source_dir_excludes". When we're done with that special content, we resume the loop and deal with the other tags.
How we identify "source_dir_excludes" within the list of existing tags is interesting. We use elementsForName, an instance method for project that returns an array of all the elements with the name given as the parameter (here "source_dir_excludes"), and since we only have one here, we specify that we want to work with that array's "firstObject". That way we create a reference to the NSXMLElement "source_dir_excludes" that we can later use to add children to it. It took me a while to figure that out. Thank you Shane :)
The creation of the list of <mask> tags is straightforward and does introduce any new concept.
Foundation reference:
*)
set theData to theProject's XMLDataWithOptions:((get current application's NSXMLNodePrettyPrint) + (get current application's NSXMLDocumentTidyXML))
theData's writeToFile:(item 16 of ParametersList) options:(current application's NSDataWritingAtomic) |error|:(missing value)
(*
We're reaching the end of this XML generation code. Now we need to output the data, in readable form.

"XMLDataWithOptions" is a method that returns an NSData object and the options are listed under "NSXMLNodeOptions". The description says, "One or more options (bit-OR'd if multiple) to affect the output of the document..." where "bit-OR'd if multiple" means "added". Another thing to notice is that the second and following options (if any) seem to need a "get". Adding the "get" to the first option does not seem to be necessary.

The options we use here are: "NSXMLNodePrettyPrint" and "NSXMLDocumentTidyXML". The first "prints this node with extra space for readability", the second "changes malformed XML into valid XML during processing of the document."
"writeToFile:options:error:" is a three part instance method of NSData, which theData is. writeToFile requires a Unix absolute path, options work the same way as defined the line before: "NSDataWritingAtomic" is "A hint to write data to an auxiliary file first and then exchange the files." And error gives us information about errors while using the method. To avoid naming conflicts with AppleScript's "error", it is put between vertical bars: |error|. The description of "error" says we can use a "NULL" value, for which the AppleScript equivalent is missing value (Everyday AppleScriptObjC, p. 52 "The terms nil and its close relative NULL are used commonly in Cocoa.  They are essentially the equivalent of AppleScript’s missing value, and the scripting bridge converts between them.")
Foundation reference:
*)
end CreateOmegaTProjectFile

(*
Let's now test the code with the following values:
*)

set ProjectSettings to {"PATH_1", "MASKS", "PATH_2", "PATH_3", "PATH_4", "FILE_5", "PATH_6", "LANG_7", "LANG_8", "TOKENIZER_9", "TOKENIZER_10", "SEGMENTATION_11", "DEFAULT_TRANSLATION_12", "REMOVE_TAGS_13", "EXTERNAL_COMMAND_14", ((POSIX path of (path to desktop folder)) & "omegat.project")}


my CreateOmegaTProjectFile(ProjectSettings)

Popular, if not outdated, posts...

.docx .NET .pptx .sdf .xlsx AASync accented letters Accessibility Accessibility Inspector Alan Kay alignment Apple AppleScript ApplescriptObjC AppleTrans applications Aquamacs Arabic archive Automator backup bash BBEdit Better Call Saul bug Butler C Calculator Calendar Chinese Cocoa Command line CSV CSVConverter database defaults Devon Dictionary DITA DocBook Dock Doxygen EDICT Emacs emacs lisp ergonomics Excel external disk file formats file system File2XLIFF4j Finder Fink Font français Free software FSF Fun Get A Mac git GNU GPL Guido Van Rossum Heartsome Homebrew HTML IceCat Illustrator InDesign input system ITS iWork Japanese Java Java Properties Viewer Java Web Start json keybindings keyboard Keynote killall launchd LISA lisp locale4j localisation MacPorts Mail markdown MARTIF to TBX Converter Maxprograms Mono MS Office NeoOffice Numbers OASIS Ocelot ODF Okapi OLPC OLT OmegaT OnMyCommand oo2po OOXML Open Solaris OpenDocument OpenOffice.org OpenWordFast org-mode OSX Pages PDF PDFPen PlainCalc PO Preview programming python QA Quick Look QuickSilver QuickTime Player Rainbow RAM reggy regular expressions review rsync RTFCleaner Safari Santa Claus scanner Script Debugger Script Editor scripting scripting additions sdf2txt security Services shell shortcuts Skim sleep Smultron Snow Leopard Spaces Spanish spellchecking Spotlight SRX standards StarOffice Stingray Study SubEthaEdit Swordfish System Events System Preferences Tags TBX TBXMaker Terminal text editing TextEdit TextMate TextWrangler The Tool Kit Time Capsule Time Machine tmutil TMX TMX Editor TMXValidator transifex Translate Toolkit translation Transmug troubleshooting TS TTX TXML UI Browser UI scripting Unix VBA vi Virtaal VirtualBox VLC W3C web WebKit WHATWG Windows Wine Word WordFast wordpress writing Xcode XLIFF xml XO xslt YAML ZFS Zip Zotero