-----------------------------------------------------------------------
-- ABW exporter in Lua for SciTE
-- Version 0.9.3, 20070805
--
-- Copyright 2005-2007 by Kein-Hong Man
-- All Rights Reserved
--
-- Permission to use, copy, modify, and distribute this software and
-- its documentation for any purpose and without fee is hereby granted,
-- provided that the above copyright notice appear in all copies and
-- that both that copyright notice and this permission notice appear
-- in supporting documentation.
--
-- KEIN-HONG MAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-- INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
-- NO EVENT SHALL KEIN-HONG MAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
-----------------------------------------------------------------------
-- USAGE
--
-- * Style hierarchy is as follows: the default SciTE style (style 32)
-- is based on Normal, and all other styles is based on the default
-- SciTE style. So the SciTE style hierarchy is preserved while the
-- Normal style is unaffected for normal text editing.
-- * If your file uses UTF-8, then they are passed verbatim to AbiWord,
-- by changing UTF-8 sequences into hhhh; entities. No error
-- correction is done, so it is up to the user to ensure correctness.
-- * UCS-2 currently not handled, while 8-bit values >127 are simply
-- converted to entities without any specific charset conversion. You
-- might need to use a charset converter to do things correctly.
-- * Control characters are shown as numbers in << >> delimiters.
-- (Same mechanism as SXW exporter.)
--
-----------------------------------------------------------------------
-- OPTIONS
--
-- * Paste them into your SciTEUser.properties file or equivalent in
-- order to make adjustments to the output. The property values given
-- are the script defaults.
-- * In the "configurable stuff" section in this script:
-- (1) Table ABW.FONT_SUBSTITUTE allows you to substitute fonts
-- (2) Table ABW.PAPER_LIST is a table of paper sizes
--
-- * These are 0/1-style false/true properties:
--[[
# if 1, use document's own tab width to format output
export.abw.doctabs=1
# if 1, convert tabs to spaces (width in "tabsize" property)
export.abw.converttabs=1
# if 1, explicitly specify whites (for opaque whites)
export.abw.transparent=1
# if 1, output used styles only
export.abw.styleused=1
# if 1, enable background colouring
export.abw.background=1
# if 0, disables font size, bold, italics, underline styles
# to disable fonts, use export.abw.monofont
export.abw.wysiwyg=1
# if 1, enable colours in exported documents
export.abw.colour=1
# if 0, don't apply a text style to whitespace-only segments
export.abw.spacestyled=0
]]
--
-- * These are properties that require values:
--[[
# magnification (added to screen font size)
export.abw.magnification=0
# if a font is specified, this overrides all other fonts, can be
# used as a monospace font mode enabler
export.abw.monofont=Courier New
# portrait or landscape page orientation
export.abw.orientation=portrait
# type of paper or a "width, height" pair with proper units:
# export.abw.pagesize=210.0mm,297.0mm
# or a paper name from a list (case-insensitive):
export.abw.pagesize=A4
# margins: "top, bottom, left, right", with proper units
# units are usually in "in" or "mm"
export.abw.margins=1.0in,1.0in,1.0in,1.0in
]]
-----------------------------------------------------------------------
-- a simple check to alert of namespace collision, but allows different
-- files to define their own functions in the exporters table
-----------------------------------------------------------------------
if exporters then
if exporters.SaveToABW then
error("SciTE_ExporterABW: exporters.SaveToABW already defined")
end
-- set pass-through if charset translation not initialized
if not exportutil.CharsetFromSet then
exportutil.CharsetFromSet = function(c) return c end
end
else
exporters = {} -- create table
end
-----------------------------------------------------------------------
-- configurable stuff
-----------------------------------------------------------------------
local ABW = {}
-- page settings defaults (please override using SciTE properties)
ABW.TABSIZE = 4
ABW.PAPER = "A4"
ABW.ORIENTATION = "portrait"
ABW.MARGINS = "1.0in,1.0in,1.0in,1.0in"
-- these are place here for easy editing
ABW.GENERATOR = "SciTE"
ABW.DC_FORMAT ="application/x-abiword"
-----------------------------------------------------------------------
-- font substitution table
-----------------------------------------------------------------------
ABW.FONT_SUBSTITUTE = {
--["Verdana"] = "Arial", -- example: switch all Verdana to Arial
--[""] = "",
}
-----------------------------------------------------------------------
-- paper name substitution table
-- Sizes are from fp_PageSize.cpp in the AbiWord sources, RH9 distro.
-- The sources are quite old, this list may have changed since then.
-- Margin values were removed; they are more useful for envelopes.
-- * uncomment more if you need unusual sizes
-----------------------------------------------------------------------
ABW.PAPER_LIST = {
-- the A sizes
--["4a"] = "1682.0, 2378.0, mm",
--["2a"] = "1189.0, 1682.0, mm",
--["a0"] = "841.0, 1189.0, mm",
--["a1"] = "594.0, 841.0, mm",
--["a2"] = "420.0, 594.0, mm",
["a3"] = "297.0, 420.0, mm",
["a4"] = "210.0, 297.0, mm",
["a5"] = "148.0, 210.0, mm",
--["a6"] = "105.0, 148.0, mm",
--["a7"] = "74.0, 105.0, mm",
--["a8"] = "52.0, 74.0, mm",
--["a9"] = "37.0, 52.0, mm",
--["a10"] = "26.0, 37.0, mm",
-- the B sizes
--["4b"] = "2000.0, 2828.0, mm",
--["2b"] = "1414.0, 2000.0, mm",
--["b0"] = "1000.0, 1414.0, mm",
--["b1"] = "707.0, 1000.0, mm",
--["b2"] = "500.0, 707.0, mm",
--["b3"] = "353.0, 500.0, mm",
["b4"] = "250.0, 353.0, mm",
["b5"] = "176.0, 250.0, mm",
["b6"] = "125.0, 176.0, mm",
--["b7"] = "88.0, 125.0, mm",
--["b8"] = "62.0, 88.0, mm",
--["b9"] = "44.0, 62.0, mm",
--["b10"] = "31.0, 44.0, mm",
-- the C sizes
--["c0"] = "917.0, 1297.0, mm",
--["c1"] = "648.0, 917.0, mm",
--["c2"] = "458.0, 648.0, mm",
["c3"] = "324.0, 458.0, mm",
["c4"] = "229.0, 324.0, mm",
-- FIXME: C5 is dealt with below, under envelopes
-- FIXME: should prolly have C6/C5 here too, for completeness
["c6"] = "114.0, 162.0, mm",
--["c7"] = "81.0, 114.0, mm",
--["c8"] = "57.0, 81.0, mm",
--["c9"] = "40.0, 57.0, mm",
--["c10"] = "28.0, 40.0, mm",
-- Japanese B sizes
-- FIXME: should prolly have the other Japanese sizes
["b5-japan"] = "182.0, 258.0, mm",
-- the rest
["legal"] = "8.5, 14.0, in",
["folio"] = "8.5, 13.0, in",
["letter"] = "8.5, 11.0, in",
["half-letter"] = "8.5, 5.5, in",
["executive"] = "7.5, 10.0, in",
["tabloid/ledger"] = "280.1, 267.0, mm",
--["monarch"] = "99.0, 191.0, mm",
--["superb"] = "297.0, 433.0, mm",
--["envelope-commercial"] = "105.0, 242.0, mm",
--["envelope-monarch"] = "99.0, 191.0, mm",
--["envelope-dl"] = "110.0, 220.0, mm",
--["envelope-c5"] = "162.0, 229.0, mm",
--["europostcard"] = "105.0, 148.0, mm",
--["custom"] = "0.0, 0.0, mm",
}
------------------------------------------------------------------------
-- Declarations for pieces of ABW document
-- * these were extracted from AbiWord 2.2.2 sample documents
-- * table.concat adds a newline to each line, no need extra newlines
------------------------------------------------------------------------
ABW.XML_HEADER = [[
]]
------------------------------------------------------------------------
-- Comment (unused in this exporter; not required)
------------------------------------------------------------------------
ABW.COMMENT = [[
]]
------------------------------------------------------------------------
-- Main enclosing body tag of ABW document
------------------------------------------------------------------------
ABW.ABIWORD_TAG = [[
]]
ABW.STYLE_NORMAL = [[
]]
ABW.S_TAG = [[
]]
-- broken into multiple lines for easier editing
ABW.PROPS_NORMAL =
"text-indent:0in; margin-top:0pt; margin-left:0pt; font-stretch:normal; "..
"line-height:1.000000; text-align:left; font-variant:normal; lang:en-US; "..
"dom-dir:ltr; margin-bottom:0pt; text-decoration:none; font-weight:normal; "..
"bgcolor:transparent; color:000000; text-position:normal; font-size:12pt; "..
"margin-right:0pt; font-style:normal; widows:2; font-family:Times New Roman"
ABW.PAGESIZE_TAG = [[
]]
ABW.SECTION_TAG = [[
]]
-----------------------------------------------------------------------
-- exporters:SaveToABW
--
-- Exports the document in the current window to an ABW format file.
--
-----------------------------------------------------------------------
function exporters:SaveToABW()
---------------------------------------------------------------------
-- XML standard entities (always present; part of XML standard)
---------------------------------------------------------------------
local entities = {
["&"] = "&", ["<"] = "<", [">"] = ">",
-- AbiWord does not escape these; they can be optionally enabled
--["'"] = "'", ["\""] = """,
}
---------------------------------------------------------------------
-- text selection
---------------------------------------------------------------------
local selBeg, selEnd = exportutil.GetSelPos()
editor:Colourise(0, -1)
---------------------------------------------------------------------
-- UTF-8 encoding flag (UCS-2 currently not handled), boolean
---------------------------------------------------------------------
local UTF8_ON = editor.CodePage == SC_CP_UTF8
---------------------------------------------------------------------
-- tab to space value
---------------------------------------------------------------------
local tabSize = tonumber(props["tabsize"])
if not tabSize or tabSize <= 0 or tabSize >= 99 then
tabSize = ABW.TABSIZE
end
if exportutil.propbool("export.abw.doctabs", 1) then
tabSize = editor.TabWidth
end
---------------------------------------------------------------------
-- boolean flags
---------------------------------------------------------------------
local convertTabs = exportutil.propbool("export.abw.converttabs", 1)
local transparent = exportutil.propbool("export.abw.transparent", 1)
local stylesUsed = exportutil.propbool("export.abw.styleused", 1)
local background = exportutil.propbool("export.abw.background", 1)
local wysiwyg = exportutil.propbool("export.abw.wysiwyg", 1)
local colour = exportutil.propbool("export.abw.colour", 1)
local spaceStyled = exportutil.propbool("export.abw.spacestyled")
---------------------------------------------------------------------
-- valued properties
---------------------------------------------------------------------
local page = {}
---------------------------------------------------------------------
-- returns comma-separated fields in a property value
local function parseProp(propValue)
local propList = {}
if not propValue or propValue == "" then return propList end
for v in string.gfind(propValue, "([^,]+),?") do
table.insert(propList, v)
end
return unpack(propList)
end
---------------------------------------------------------------------
-- retrieve units from a length string
local function getUnit(v)
local _, _, num, unit = string.find(v, "^(%A+)(%a+)$")
return num, unit
end
---------------------------------------------------------------------
-- magnification value to add to screen font size
local magnification = tonumber(props["export.abw.magnification"]) or 0
-- font override property
local monoFont = props["export.abw.monofont"]
if not monoFont or monoFont == "" then monoFont = nil end
---------------------------------------------------------------------
-- page orientation
page.orientation = string.lower(props["export.abw.orientation"])
if not page.orientation or page.orientation == "" then
if page.orientation ~= "portrait" and page.orientation ~= "landscape" then
page.orientation = ABW.ORIENTATION
end
end
---------------------------------------------------------------------
-- paper size: paper name or width, height
page.type = string.lower(props["export.abw.pagesize"])
local pageInfo = ABW.PAPER_LIST[page.type]
if pageInfo or (not page.type or page.type == "") then
-- set to default size if paper name not specified
if not pageInfo then
page.type = ABW.PAPER
pageInfo = ABW.PAPER_LIST[string.lower(page.type)]
end
-- extract preset paper information
_, _, page.height, page.width, page.units =
string.find(pageInfo, "^(%S+)%s*,%s*(%S+)%s*,%s*(%a+)$")
-- swap width & height if landscape
if page.orientation == "landscape" then
page.height, page.width = page.width, page.height
end
else
-- no paper name, assume custom, get size from properties
page.width, page.height = parseProp(page.type)
if not page.width or not page.height then
error("custom page size not specified properly in export.abw.pagesize")
end
-- split units and make sure they are identical
page.width, page.u1 = getUnit(page.width)
page.height, page.u2 = getUnit(page.height)
if page.u1 ~= page.u2 then
error("units must match for lengths in export.abw.pagesize")
end
page.units = page.u1
page.type = "Custom"
end
---------------------------------------------------------------------
-- page margins: top, bottom, left, right
page.top, page.bottom, page.left, page.right =
parseProp(props["export.abw.margins"])
if not page.top or not page.bottom or not page.left or not page.right then
page.top, page.bottom, page.left, page.right =
parseProp(ABW.MARGINS)
end
-- fill in dummy values for header and footer margins
page.header = page.top
page.footer = page.bottom
---------------------------------------------------------------------
-- handle font substitution
---------------------------------------------------------------------
local function font_actual(font)
local subst = ABW.FONT_SUBSTITUTE[font]
if subst then return subst end
return font
end
---------------------------------------------------------------------
-- dump all styles or only styles that are used
---------------------------------------------------------------------
local styleIsUsed = {}
if stylesUsed then
for i = selBeg, selEnd - 1 do
styleIsUsed[exportutil.StyleAt(i)] = true
end
else
for i = 0, exportutil.STYLE_MAX do
styleIsUsed[i] = true
end
end
styleIsUsed[exportutil.STYLE_DEFAULT] = true
---------------------------------------------------------------------
-- style handling
---------------------------------------------------------------------
stylemgr:setlexer(editor.Lexer)
-- helper functions for processing style information into XML specs
local function IsSpecified(sd, field)
return (string.find(sd.specified, field, 1, 1))
end
local function AddProp(style, prop)
if style == "" then return prop end
return style.."; "..prop
end
local function HexStr(colourSpec)
local hex = string.sub(stylemgr:hexstr(colourSpec), 2)
if transparent and hex == "FFFFFF" then return "transparent" end
return hex
end
-- build style specifications
local style = {}
local styleName = {}
local stylePrefix = (stylemgr:getlexerlanguage() or "txt").."_S"
for i = 0, exportutil.STYLE_MAX do
if styleIsUsed[i] then
-- read and process style entry
local sd = stylemgr:locate(i)
styleName[i] = [[]]
-- start of style specification
local st = ""
if sd.specified ~= "" then
-- handle font naming and monofont override
if IsSpecified(sd, "font") then
local actual_font
if not monoFont then
actual_font = font_actual(sd.font)
elseif monoFont and i == exportutil.STYLE_DEFAULT then
actual_font = font_actual(monoFont)
end
if actual_font then
st = AddProp(st, "font-family:"..actual_font)
end
end
-- handle font properties optionally
if wysiwyg or i == exportutil.STYLE_DEFAULT then
if IsSpecified(sd, "size") then
sd.size = sd.size + magnification
st = AddProp(st, "font-size:"..sd.size.."pt")
end
end
if wysiwyg then
if IsSpecified(sd, "italics") then
local v = sd.italics and "italic" or "normal"
st = AddProp(st, "font-style:"..v)
end
if IsSpecified(sd, "bold") then
local v = sd.bold and "bold" or "normal"
st = AddProp(st, "font-weight:"..v)
end
if IsSpecified(sd, "underlined") then
local v = sd.underlined and "underline" or "none"
st = AddProp(st, "text-decoration:"..v)
end
end--if wysiwyg
--if IsSpecified(sd, "eolfilled") and sd.eolfilled then
-- TODO low priority; is this even possible?
--end
-- handle foreground and background optionally
if colour then
local c = HexStr(sd.fore)
if IsSpecified(sd, "fore") then
st = AddProp(st, "color:"..c)
end
c = HexStr(sd.back)
if background and IsSpecified(sd, "back") then
st = AddProp(st, "bgcolor:"..c)
end
end--if colour
end
-- determine style hierarchy: Normal -> style 32 -> other styles
local basedon = stylePrefix..(exportutil.STYLE_DEFAULT)
if i == exportutil.STYLE_DEFAULT then
basedon = "Normal"
end
-- end of style specification, build actual XML line
style[i] = string.format(ABW.S_TAG, stylePrefix..i, basedon, st)
end--if styleIsUsed
end--for i
---------------------------------------------------------------------
-- open exported file for saving
---------------------------------------------------------------------
local saveName = exportutil.exportfile(props["FileDir"], props["FileName"], "abw")
local fp = io.open(saveName, "wb")
if not fp then
error("exporters:SaveToABW: could not save file \""..saveName.."\"")
end
if exportutil.VERBOSE ~= 0 then
_ALERT("\nExporting to ABW, filepath: "..saveName)
end
---------------------------------------------------------------------
-- build file data
---------------------------------------------------------------------
-- builds a document meta information entry
local function make_meta(key, value)
return [[]]..value..[[]]
end
local data = {}
---------------------------------------------------------------------
-- xml file header
table.insert(data, ABW.XML_HEADER)
table.insert(data, ABW.ABIWORD_TAG)
--table.insert(data, ABW.COMMENT) -- not necessary
---------------------------------------------------------------------
-- metadata section
table.insert(data, [[]])
table.insert(data, make_meta("dc.format", ABW.DC_FORMAT))
table.insert(data, make_meta("abiword.generator", ABW.GENERATOR))
table.insert(data, make_meta("abiword.date_last_changed",
os.date("%a %b %d %H:%M:%S %Y"))) -- actually %C but for some reason fails
-- on my SciTE build on a Win32 machine
-- (probably MSVCRT incompatibility)
table.insert(data, [[]])
---------------------------------------------------------------------
-- style section
table.insert(data, [[]])
table.insert(data, string.format(ABW.STYLE_NORMAL, ABW.PROPS_NORMAL))
for _,styleItem in pairs(style) do
table.insert(data, styleItem)
end
table.insert(data, [[]])
---------------------------------------------------------------------
-- page information section
table.insert(data, string.format(ABW.PAGESIZE_TAG,
page.type, page.orientation,
page.width, page.height, page.units))
table.insert(data, string.format(ABW.SECTION_TAG,
page.footer, page.header,
page.right, page.left, page.top, page.bottom))
---------------------------------------------------------------------
-- main loop
---------------------------------------------------------------------
local line = {}
local seg = ""; local notwhitespace = false
local column = 0
local i = selBeg
local styleCurrent
---------------------------------------------------------------------
-- apply style to a line segment
---------------------------------------------------------------------
function styleSegment(segment)
if notwhitespace or spaceStyled then
return styleName[styleCurrent]..segment..[[]]
end
return segment
end
while i < selEnd do
local ch = exportutil.CharAt(i)
local styleNow = exportutil.StyleAt(i)
-------------------------------------------------------------------
-- handle newlines
-------------------------------------------------------------------
if ch == "\n" or ch == "\r" then
if ch == "\r" and exportutil.CharAt(i + 1) == "\n" then i = i + 1 end
table.insert(line, styleSegment(seg))
table.insert(line, [[
]])
table.insert(data, table.concat(line))
column = 0
line = {}; styleCurrent = nil
seg = ""; notwhitespace = false
end
-------------------------------------------------------------------
-- handle style change (always true at beginning of line)
-------------------------------------------------------------------
if styleNow ~= styleCurrent then
-- close previous text span if required
if column > 0 then
table.insert(line, styleSegment(seg))
else
-- start of line here
line = {[[]]}
end
seg = ""; notwhitespace = false
styleCurrent = styleNow
end--style
-------------------------------------------------------------------
-- handle tabs
-------------------------------------------------------------------
if ch == "\t" then
if convertTabs then
local ts = tabSize - column % tabSize
seg = seg..string.rep(" ", ts)
column = column + ts
else
seg = seg..ch -- normal tab
column = column + 1
end
-------------------------------------------------------------------
-- handle control characters
-------------------------------------------------------------------
else
local c = string.byte(ch)
notwhitespace = true
if c < 32 then
if ch ~= "\r" and ch ~= "\n" then
-- ALTERNATIVES control char handling seems to use style 36
-- With the method below, we can still see the value...
local ctrl = "«"..c.."»" -- "«"..c.."»"
seg = seg..ctrl
column = column + string.len(ctrl)
end
-----------------------------------------------------------------
-- handle characters with codes > 127 (conversion to UTF-8 with
-- optional charset conversion)
-----------------------------------------------------------------
elseif c > 127 then
-- ***
-- The following borks on AbiWord 2.4/Win32, so I am just using
-- a reverse-engineered alternative where no entities are used.
-- ***
-- * AbiWord file format document states that an ABW XML file must
-- be a 7-bit clean ASCII XML file. So use: "ÿ" or ""
-- and UTF8 sequences need to be changed into the above.
--[[
if UTF8_ON then
--convert a 2 or 3 byte UTF8 sequence to an entity
local u
if c < 224 then
u = math.mod(c, 32) * 64
i = i + 1; c = string.byte(exportutil.CharAt(i))
u = u + math.mod(c, 128)
else
u = math.mod(c, 16) * 4096
i = i + 1; c = string.byte(exportutil.CharAt(i))
u = u + math.mod(c, 128) * 64
i = i + 1; c = string.byte(exportutil.CharAt(i))
u = u + math.mod(c, 128)
end
ch = string.format("%04x;", u)
else
--]]
if not UTF8_ON then
-- convert to UTF-8
c = exportutil.CharsetToUTF8(c)
if c >= 2048 then
ch = string.char(224 + math.floor(c / 4096),
128 + math.floor(c / 64) % 64,
128 + c % 64)
elseif c >= 128 then
ch = string.char(192 + math.floor(c / 64),
128 + c % 64)
else
ch = string.char(c)
end
end
seg = seg..ch
column = column + 1
-----------------------------------------------------------------
-- handle normal characters
-----------------------------------------------------------------
else
seg = seg..(entities[ch] or ch)
column = column + 1
end--if c
end--if ch
i = i + 1
end--while
-- clean up if last line has no EOL
if seg ~= "" or #line > 0 then
table.insert(line, styleSegment(seg)..[[
]])
table.insert(data, table.concat(line))
else
table.insert(data, [[]])
end
-- close tags
table.insert(data, [[]])
table.insert(data, [[]])
---------------------------------------------------------------------
-- write and close exported file
---------------------------------------------------------------------
fp:write(table.concat(data, "\n"))
fp:close()
if exportutil.VERBOSE ~= 0 then
exportutil.progress("... done.")
end
end
-- end of script