git.iko.soy/typeable/pandoc
on master help sign in
Added 'odt' output option to pandoc:

Not a writer, but a module that inserts the output of the OpenDocument writer into an ODT archive. This replaces markdown2odt. + Added odt output option to Main.hs. + Added default for .odt output file. + Changed defaults so that .xml and .sgml aren't automatically DocBook. + Added odt writer to Text.Pandoc exports. + Added Text.Pandoc.ODT and included in pandoc.cabal. + Added reference.odt as data-file in pandoc.cabal. + Handle picture links in OpenDocument files using xml library. + Removed markdown2odt and references from Makefile, README, man. git-svn-id: https://pandoc.googlecode.com/svn/trunk@1345 788f1e2b-df1e-0410-8736-df70ead52e1b

commitd072ad4b66b5cdf5a8f811ae1f78460e34270d58 authorfiddlosopher <fiddlosopher@788f1e2b-df1e-0410-8736-df70ead52e1b> dateThu 31 Jul 2008, 23:16 (18 years ago) parent504a61a
8 files changed+210−144
Main.hs+31−15 Makefile+1−6 README+3−23 Text/Pandoc/ODT.hs+141 Text/Pandoc/Shared.hs+25−1 man/man1/markdown2odt.1.md−52 pandoc.cabal+9−6 wrappers/markdown2odt.in−41
Main.hs+31−15
@@ -31,11 +31,12 @@ writers.
3131module Main where
3232import Text.Pandoc
3333import Text.Pandoc.UTF8
34+import Text.Pandoc.ODT
3435import Text.Pandoc.Shared ( joinWithSep, HTMLMathMethod (..) )
3536import Text.Pandoc.Highlighting ( languages )
3637import System.Environment ( getArgs, getProgName, getEnvironment )
3738import System.Exit ( exitWith, ExitCode (..) )
38−import System.FilePath ( takeExtension )
39+import System.FilePath ( takeExtension, takeDirectory )
3940import System.Console.GetOpt
4041import System.IO
4142import Data.Maybe ( fromMaybe )
@@ -82,6 +83,7 @@ writers = [("native" , (writeDoc, ""))
8283 ,("s5" , (writeS5String, defaultS5Header))
8384 ,("docbook" , (writeDocbook, defaultDocbookHeader))
8485 ,("opendocument" , (writeOpenDocument, defaultOpenDocumentHeader))
86+ ,("odt" , (writeOpenDocument, defaultOpenDocumentHeader))
8587 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
8688 ,("context" , (writeConTeXt, defaultConTeXtHeader))
8789 ,("texinfo" , (writeTexinfo, ""))
@@ -92,6 +94,10 @@ writers = [("native" , (writeDoc, ""))
9294 ,("rtf" , (writeRTF, defaultRTFHeader))
9395 ]
9496
97+isNonTextOutput :: String -> Bool
98+isNonTextOutput "odt" = True
99+isNonTextOutput _ = False
100+
95101-- | Writer for Pandoc native format.
96102writeDoc :: WriterOptions -> Pandoc -> String
97103writeDoc _ = prettyPandoc
@@ -392,8 +398,7 @@ defaultWriterName x =
392398 ".texi" -> "texinfo"
393399 ".texinfo" -> "texinfo"
394400 ".db" -> "docbook"
395− ".xml" -> "docbook"
396− ".sgml" -> "docbook"
401+ ".odt" -> "odt"
397402 ['.',y] | y `elem` ['1'..'9'] -> "man"
398403 _ -> "html"
399404
@@ -478,10 +483,6 @@ main = do
478483 Just (w,h) -> return (w, h)
479484 Nothing -> error ("Unknown writer: " ++ writerName')
480485
481− output <- if (outputFile == "-")
482− then return stdout
483− else openFile outputFile WriteMode
484−
485486 environment <- getEnvironment
486487 let columns = case lookup "COLUMNS" environment of
487488 Just cols -> read cols
@@ -501,11 +502,13 @@ main = do
501502 tabFilter spsToNextStop (x:xs) =
502503 x:(tabFilter (spsToNextStop - 1) xs)
503504
505+ let standalone' = (standalone && not strict) || writerName' == "odt"
506+
504507 let startParserState =
505508 defaultParserState { stateParseRaw = parseRaw,
506509 stateTabStop = tabStop,
507510 stateSanitizeHTML = sanitize,
508− stateStandalone = standalone && (not strict),
511+ stateStandalone = standalone',
509512 stateSmart = smart || writerName' `elem`
510513 ["latex", "context"],
511514 stateColumns = columns,
@@ -519,16 +522,15 @@ main = do
519522 let header = (if (customHeader == "DEFAULT")
520523 then defaultHeader
521524 else customHeader) ++ csslink ++ includeHeader
522− let writerOptions = WriterOptions { writerStandalone = standalone &&
523− (not strict),
525+ let writerOptions = WriterOptions { writerStandalone = standalone',
524526 writerHeader = header,
525527 writerTitlePrefix = titlePrefix,
526528 writerTabStop = tabStop,
527529 writerTableOfContents = toc &&
528530 (not strict) &&
529− writerName/="s5",
531+ writerName' /= "s5",
530532 writerHTMLMathMethod = mathMethod,
531− writerS5 = (writerName=="s5"),
533+ writerS5 = (writerName' == "s5"),
532534 writerIgnoreNotes = False,
533535 writerIncremental = incremental,
534536 writerNumberSections = numberSections,
@@ -538,11 +540,25 @@ main = do
538540 writerReferenceLinks = referenceLinks,
539541 writerWrapText = wrap }
540542
541− (readSources sources) >>= (hPutStrLn output . toUTF8 .
543+ let writeOutput = if writerName' == "odt"
544+ then if outputFile == "-"
545+ then \_ -> do
546+ hPutStrLn stderr ("Error: Cannot write " ++ writerName ++
547+ " output to stdout.\n" ++
548+ "Specify an output file using the -o option.")
549+ exitWith $ ExitFailure 5
550+ else let sourceDirRelative = if null sources
551+ then ""
552+ else takeDirectory (head sources)
553+ in saveOpenDocumentAsODT outputFile sourceDirRelative
554+ else if outputFile == "-"
555+ then putStrLn
556+ else writeFile outputFile . (++ "\n")
557+
558+ (readSources sources) >>= writeOutput . toUTF8 .
542559 (writer writerOptions) .
543560 (reader startParserState) . tabFilter tabStop .
544− fromUTF8 . (joinWithSep "\n")) >>
545− hClose output
561+ fromUTF8 . (joinWithSep "\n")
546562
547563 where
548564 readSources [] = mapM readSource ["-"]
Makefile+1−6
@@ -24,7 +24,7 @@ EXECSBASE := $(shell sed -ne 's/^[Ee]xecutable:\{0,1\}[[:space:]]*//p' $(CABAL))
2424#-------------------------------------------------------------------------------
2525# Install targets
2626#-------------------------------------------------------------------------------
27−WRAPPERS := html2markdown markdown2pdf hsmarkdown markdown2odt
27+WRAPPERS := html2markdown markdown2pdf hsmarkdown
2828# Add .exe extensions if we're running Windows/Cygwin.
2929EXTENSION := $(shell uname | tr '[:upper:]' '[:lower:]' | \
3030 sed -ne 's/^cygwin.*$$/\.exe/p')
@@ -114,11 +114,6 @@ $(ODTREF): $(addprefix $(ODTSTYLES)/, layout-cache meta.xml styles.xml content.x
114114 cd $(ODTSTYLES) ; \
115115 zip -9 -r $(notdir $@) * -x $(notdir $@)
116116
117−ODTREFSH=$(SRCDIR)/wrappers/odtref.sh
118−cleanup_files+=$(ODTREFSH)
119−$(ODTREFSH): $(ODTREF)
120− echo "REFERENCEODT='$(PREFIX)/share/$(DATADIR)/$(notdir $(ODTREF))'" > $@
121−
122117.PHONY: wrappers
123118wrappers: $(WRAPPERS)
124119cleanup_files+=$(WRAPPERS)
README+3−23
@@ -119,7 +119,7 @@ then convert the output back to the local encoding.
119119Shell scripts
120120=============
121121
122−Four shell scripts, `markdown2pdf`, `markdown2odt`, `html2markdown`, and
122+Three shell scripts, `markdown2pdf`, `html2markdown`, and
123123`hsmarkdown`, are included in the standard Pandoc installation. (They
124124are not included in the Windows binary package, as they require a POSIX
125125shell, but they may be used in Windows under Cygwin.)
@@ -149,27 +149,7 @@ shell, but they may be used in Windows under Cygwin.)
149149 included in your LaTeX distribution, you can get them from
150150 [CTAN].
151151
152−2. `markdown2odt` produces an ODT file from markdown-formatted
153− text, using `pandoc` and `pdflatex`. (ODT is "OpenDocument
154− Text," the default format for the OpenOffice.org Writer.)
155− The default behavior of `markdown2odt` is to create a file with the
156− same base name as the first argument and the extension `odt`; thus,
157− for example,
158−
159− markdown2odt sample.txt endnotes.txt
160−
161− will produce `sample.odt`. (If `sample.odt` exists already,
162− it will be backed up before being overwritten.) An output file
163− name can be specified explicitly using the `-o` option:
164−
165− markdown2odt -o book.odt chap1 chap2
166−
167− If no input file is specified, input will be taken from STDIN.
168− All of `pandoc`'s options will work with `markdown2odt` as well.
169−
170− `markdown2odt` requires `zip`, which must be in the path.
171−
172−3. `html2markdown` grabs a web page from a file or URL and converts
152+2. `html2markdown` grabs a web page from a file or URL and converts
173153 it to markdown-formatted text, using `tidy` and `pandoc`.
174154
175155 All of `pandoc`'s options will work with `html2markdown` as well.
@@ -198,7 +178,7 @@ shell, but they may be used in Windows under Cygwin.)
198178 It uses [`iconv`] for character encoding conversions; if `iconv`
199179 is absent, it will still work, but it will treat everything as UTF-8.
200180
201−4. `hsmarkdown` is designed to be used as a drop-in replacement for
181+3. `hsmarkdown` is designed to be used as a drop-in replacement for
202182 `Markdown.pl`. It forces `pandoc` to convert from markdown to
203183 HTML, and to use the `--strict` flag for maximal compliance with
204184 official markdown syntax. (All of Pandoc's syntax extensions and
Text/Pandoc/ODT.hsnew file+141
@@ -0,0 +1,141 @@
1+{-
2+Copyright (C) 2008 John MacFarlane <jgm@berkeley.edu>
3+
4+This program is free software; you can redistribute it and/or modify
5+it under the terms of the GNU General Public License as published by
6+the Free Software Foundation; either version 2 of the License, or
7+(at your option) any later version.
8+
9+This program is distributed in the hope that it will be useful,
10+but WITHOUT ANY WARRANTY; without even the implied warranty of
11+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+GNU General Public License for more details.
13+
14+You should have received a copy of the GNU General Public License
15+along with this program; if not, write to the Free Software
16+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17+-}
18+
19+{- |
20+ Module : Text.Pandoc.ODT
21+ Copyright : Copyright (C) 2006-7 John MacFarlane
22+ License : GNU GPL, version 2 or above
23+
24+ Maintainer : John MacFarlane <jgm@berkeley.edu>
25+ Stability : alpha
26+ Portability : portable
27+
28+Functions for producing an ODT file from OpenDocument XML.
29+-}
30+module Text.Pandoc.ODT ( saveOpenDocumentAsODT ) where
31+import Data.Maybe ( fromJust )
32+import Data.List ( partition, intersperse )
33+import Prelude hiding ( writeFile, readFile )
34+import System.IO.UTF8
35+import System.IO ( stderr )
36+import System.Directory
37+import System.FilePath ( (</>), takeDirectory, takeFileName, splitDirectories )
38+import System.Process ( runCommand, waitForProcess )
39+import System.Exit
40+import Text.XML.Light
41+import Text.XML.Light.Cursor
42+import Text.Pandoc.Shared ( withTempDir )
43+import Network.URI ( isURI )
44+import Paths_pandoc
45+
46+-- | Produce an ODT file from OpenDocument XML.
47+saveOpenDocumentAsODT :: FilePath -- ^ Pathname of ODT file to be produced.
48+ -> FilePath -- ^ Relative directory of source file.
49+ -> String -- ^ OpenDocument XML contents.
50+ -> IO ()
51+saveOpenDocumentAsODT destinationODTPath sourceDirRelative xml = do
52+ let zipCmd = "zip"
53+ -- check for zip in path:
54+ maybeZipPath <- findExecutable zipCmd
55+ let zipPath = case maybeZipPath of
56+ Just p -> p
57+ Nothing -> error $ "The '" ++ zipCmd ++ "' command, which is needed to build an ODT file, " ++
58+ "was not found.\n" ++
59+ "It can be obtained from http://www.info-zip.org/Zip.html\n" ++
60+ "Debian (and Debian-based) linux: apt-get install zip\n" ++
61+ "Windows: See http://gnuwin32.sourceforge.net/packages/zip.htm"
62+ referenceODTPath <- getDataFileName "reference.odt"
63+ withTempDir "pandoc-odt" $ \tempDir -> do
64+ let tempODT = tempDir </> "reference.odt"
65+ copyFile referenceODTPath tempODT
66+ createDirectory $ tempDir </> "Pictures"
67+ xml' <- handlePictures tempODT sourceDirRelative xml
68+ writeFile (tempDir </> "content.xml") xml'
69+ oldDir <- getCurrentDirectory
70+ setCurrentDirectory tempDir
71+ let zipCmdLine = zipPath ++ " -9 -q -r " ++ tempODT ++ " " ++ "content.xml Pictures"
72+ ec <- runCommand zipCmdLine >>= waitForProcess -- this requires compilation with -threaded
73+ setCurrentDirectory oldDir
74+ case ec of
75+ ExitSuccess -> copyFile tempODT destinationODTPath
76+ _ -> error "Error creating ODT." >> exitWith ec
77+
78+
79+-- | Find <draw:image ... /> elements and copy the file (xlink:href attribute) into Pictures/ in
80+-- the zip file. If filename is a URL, attempt to download it. Modify xlink:href attributes
81+-- to point to the new locations in Pictures/. Return modified XML.
82+handlePictures :: FilePath -- ^ Path of ODT file in temp directory
83+ -> FilePath -- ^ Directory (relative) containing source file
84+ -> String -- ^ OpenDocument XML string
85+ -> IO String -- ^ Modified XML
86+handlePictures tempODT sourceDirRelative xml = do
87+ let parsed = case parseXMLDoc xml of
88+ Nothing -> error "Could not parse OpenDocument XML."
89+ Just x -> x
90+ let cursor = case (fromForest $ elContent parsed) of
91+ Nothing -> error "ODT appears empty"
92+ Just x -> x
93+ cursor' <- scanPictures tempODT sourceDirRelative cursor
94+ let modified = parsed { elContent = toForest $ root cursor' }
95+ putStrLn $ showTopElement modified
96+ return $ showTopElement modified
97+
98+scanPictures :: FilePath -> FilePath -> Cursor -> IO Cursor
99+scanPictures tempODT sourceDirRelative cursor = do
100+ cursor' <- handleTree tempODT sourceDirRelative cursor
101+ case right cursor' of
102+ Just n -> scanPictures tempODT sourceDirRelative n
103+ Nothing -> return cursor'
104+
105+handleTree :: FilePath -> FilePath -> Cursor -> IO Cursor
106+handleTree tempODT sourceDirRelative cursor = do
107+ case firstChild cursor of
108+ Nothing -> modifyContentM (handleContent tempODT sourceDirRelative) cursor
109+ Just n -> scanPictures tempODT sourceDirRelative n >>= return . fromJust . parent
110+
111+-- | If content is an image link, handle it appropriately.
112+-- Otherwise, handle children if any.
113+handleContent :: FilePath -> FilePath -> Content -> IO Content
114+handleContent tempODT sourceDirRelative content@(Elem el) = do
115+ if qName (elName el) == "image"
116+ then do
117+ let (hrefs, rest) = partition (\a -> qName (attrKey a) == "href") $ elAttribs el
118+ let href = case hrefs of
119+ [] -> error $ "No href found in " ++ show el
120+ [x] -> x
121+ _ -> error $ "Multiple hrefs found in " ++ show el
122+ if isURI $ attrVal href
123+ then return content
124+ else do -- treat as filename
125+ let oldLoc = sourceDirRelative </> attrVal href
126+ fileExists <- doesFileExist oldLoc
127+ if fileExists
128+ then do
129+ let pref = take 230 $ concat $ intersperse "_" $
130+ splitDirectories $ takeDirectory $ attrVal href
131+ let newLoc = "Pictures" </> pref ++ "_" ++ (takeFileName $ attrVal href)
132+ let tempDir = takeDirectory tempODT
133+ copyFile oldLoc $ tempDir </> newLoc
134+ let newAttrs = (href { attrVal = newLoc }) : rest
135+ return $ Elem (el { elAttribs = newAttrs })
136+ else do
137+ hPutStrLn stderr $ "Warning: Unable to find image at " ++ oldLoc ++ " - ignoring."
138+ return content
139+ else return content
140+
141+handleContent _ _ c = return c -- not Element
Text/Pandoc/Shared.hs+25−1
@@ -98,7 +98,9 @@ module Text.Pandoc.Shared (
9898 -- * Writer options
9999 HTMLMathMethod (..),
100100 WriterOptions (..),
101− defaultWriterOptions
101+ defaultWriterOptions,
102+ -- * File handling
103+ withTempDir
102104 ) where
103105
104106import Text.Pandoc.Definition
@@ -110,6 +112,9 @@ import Data.Char ( toLower, toUpper, ord, isLower, isUpper )
110112import Data.List ( find, isPrefixOf )
111113import Control.Monad ( join )
112114import Network.URI ( parseURI, URI (..), isAllowedInURI )
115+import System.FilePath ( (</>), (<.>) )
116+import System.IO.Error ( catch, ioError, isAlreadyExistsError )
117+import System.Directory
113118
114119--
115120-- List processing
@@ -900,3 +905,22 @@ defaultWriterOptions =
900905 , writerReferenceLinks = False
901906 , writerWrapText = True
902907 }
908+
909+-- | Perform a function in a temporary directory and clean up.
910+withTempDir :: FilePath -> (FilePath -> IO a) -> IO a
911+withTempDir baseName func = do
912+ tempDir <- createTempDir 0 baseName
913+ result <- catch (func tempDir) $ \e -> removeDirectoryRecursive tempDir >> ioError e
914+ removeDirectoryRecursive tempDir
915+ return result
916+
917+-- | Create a temporary directory with a unique name.
918+createTempDir :: Integer -> FilePath -> IO FilePath
919+createTempDir num baseName = do
920+ sysTempDir <- getTemporaryDirectory
921+ let dirName = sysTempDir </> baseName <.> show num
922+ catch (createDirectory dirName >> return dirName) $
923+ \e -> if isAlreadyExistsError e
924+ then createTempDir (num + 1) baseName
925+ else ioError e
926+
man/man1/markdown2odt.1.mddeleted−52
@@ -1,52 +0,0 @@
1−% MARKDOWN2ODT(1) Pandoc User Manuals
2−% John MacFarlane and Recai Oktas
3−% March 14, 2008
4−
5−# NAME
6−
7−markdown2odt - converts markdown-formatted text to ODT
8−
9−# SYNOPSIS
10−
11−markdown2odt [*options*] [*input-file*]...
12−
13−# DESCRIPTION
14−
15−`markdown2odt` converts *input-file* (or text from standard
16−input) from markdown-formatted plain text to ODT (OpenDocument
17−Text) format. If no output filename is specified (using the `-o`
18−option), the name of the output file is derived from the input file;
19−thus, for example, if the input file is *hello.txt*, the output file
20−will be *hello.odt*. If the input is read from STDIN and no output
21−filename is specified, the output file will be named *stdin.odt*. If
22−multiple input files are specified, they will be concatenated before
23−conversion, and the name of the output file will be derived from the
24−first input file.
25−
26−Input is assumed to be in the UTF-8 character encoding. If your
27−local character encoding is not UTF-8, you should pipe input
28−through `iconv`:
29−
30− iconv -t utf-8 input.txt | markdown2odt
31−
32−# OPTIONS
33−
34−`markdown2odt` is a wrapper around `pandoc`, so all of
35−`pandoc`'s options can be used with `markdown2odt` as well.
36−See `pandoc`(1) for a complete list.
37−The following options are most relevant:
38−
39−-o *FILE*, \--output=*FILE*
40−: Write output to *FILE*.
41−
42−\--strict
43−: Use strict markdown syntax, with no extensions or variants.
44−
45−-S, \--smart
46−: Use smart quotes, dashes, and ellipses. (This option is significant
47− only when the input format is `markdown`. It is selected automatically
48− when the output format is `latex` or `context`.)
49−
50−# SEE ALSO
51−
52−`pandoc`(1)
pandoc.cabal+9−6
@@ -57,6 +57,7 @@ Extra-Source-Files: README, INSTALL, COPYRIGHT, COPYING,
5757Extra-Tmp-Files: Text/Pandoc/ASCIIMathML.hs,
5858 Text/Pandoc/DefaultHeaders.hs,
5959 Text/Pandoc/Writers/S5.hs
60+Data-Files: odt-styles/reference.odt
6061Flag splitBase
6162 Description: Choose the new, smaller, split-up base package.
6263 Default: True
@@ -72,7 +73,8 @@ Library
7273 if flag(highlighting)
7374 Build-depends: highlighting-kate
7475 cpp-options: -DHIGHLIGHTING
75− Build-Depends: parsec < 3, xhtml, mtl, network, filepath
76+ Build-Depends: parsec < 3, xhtml, mtl, network, filepath, process, directory, xml,
77+ bytestring, binary
7678 Hs-Source-Dirs: .
7779 Exposed-Modules: Text.Pandoc,
7880 Text.Pandoc.Blocks,
@@ -80,6 +82,7 @@ Library
8082 Text.Pandoc.CharacterReferences,
8183 Text.Pandoc.Shared,
8284 Text.Pandoc.UTF8,
85+ Text.Pandoc.ODT,
8386 Text.Pandoc.ASCIIMathML,
8487 Text.Pandoc.DefaultHeaders,
8588 Text.Pandoc.Highlighting,
@@ -101,11 +104,11 @@ Library
101104 Text.Pandoc.Writers.RTF,
102105 Text.Pandoc.Writers.S5
103106 Other-Modules: Text.Pandoc.XML
104− Ghc-Options: -O2 -Wall
107+ Ghc-Options: -O2 -Wall -threaded
105108 Ghc-Prof-Options: -auto-all
106109
107110Executable pandoc
108− Hs-Source-Dirs: .
109− Main-Is: Main.hs
110− Ghc-Options: -O2 -Wall
111− Ghc-Prof-Options: -auto-all
111+ Hs-Source-Dirs: .
112+ Main-Is: Main.hs
113+ Ghc-Options: -O2 -Wall -threaded
114+ Ghc-Prof-Options: -auto-all
wrappers/markdown2odt.indeleted−41
@@ -1,41 +0,0 @@
1−#!/bin/sh -e
2−
3−REQUIRED="zip"
4−SYNOPSIS="converts markdown-formatted text to ODT."
5−
6−### odtref.sh
7−
8−### common.sh
9−
10−### tempdir.sh
11−
12−if [ "$OUTPUT" = "-" ]; then
13− firstinfile="$(echo $ARGS | sed -ne '1p')"
14− firstinfilebase="${firstinfile%.*}"
15− destname="${firstinfilebase:-stdin}.odt"
16−else
17− destname="$OUTPUT"
18−fi
19−
20−(
21− cp $REFERENCEODT $THIS_TEMPDIR/new.odt
22− pandoc -s -r markdown -w opendocument "$@" -o $THIS_TEMPDIR/content.xml
23− zip -9 -j $THIS_TEMPDIR/new.odt $THIS_TEMPDIR/content.xml
24−) || exit $?
25−
26−is_target_exists=
27−if [ -f "$destname" ]; then
28− is_target_exists=1
29− mv "$destname" "$destname~"
30−fi
31−
32−mv -f $THIS_TEMPDIR/new.odt "$destname"
33−
34−errn "Created $destname"
35−[ -z "$is_target_exists" ] || {
36− errn " (previous file has been backed up as $destname~)"
37−}
38−
39−err .
40−
41−