step2-base.xsl 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!--
  3. This file is part of the DITA Open Toolkit project.
  4. Copyright 2004, 2006 IBM Corporation
  5. See the accompanying LICENSE file for applicable license.
  6. -->
  7. <!-- Second step in the DITA to text transform. This takes an intermediate
  8. format, and converts it to text output. The text style is determined by
  9. the OUTFORMAT parameter. Currently supported values are plaintext, troff,
  10. and nroff (troff and nroff match at the moment).
  11. The first step creates an intermediate format that uses only a few elements.
  12. It has a root <dita> element, and everything else fits in to these elements:
  13. <section> : used for <section> and <example>. This can nest any of the following elements.
  14. <sectiontitle> : used for the titles of <section> and <example>. This will nest the <text> element.
  15. <block> : all other block-like elements. The reason section does not use <block>
  16. is that it maps well to troff-style sections that use the .SH macro
  17. for highlighting and indenting. This can nest any number of <block>
  18. or <text> elements. Attributes set lead-in text (such as list item numbers
  19. that must appear before the list item text), as well as indent values.
  20. Other attributes are described below.
  21. <text> : all text nodes and phrases. This can include text or additional <text> elements.
  22. Text will be wrapped, with the width determined by the LINELENGTH parameter.
  23. Formatters such as troff may reflow the text as needed. Line breaks should only
  24. be forced in pre-formatted text, or between blocks.
  25. -->
  26. <xsl:stylesheet version="2.0"
  27. xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  28. >
  29. <!--
  30. ALL ELEMENTS CAN TAKE @xtrf and @xtrc
  31. Attributes on dita:
  32. Attributes on section:
  33. Attributes on sectiontitle:
  34. Attributes on block:
  35. @xml:space="preserve"
  36. @position="center"
  37. @indent="digit" - additional indent new for this element
  38. @compact="yes|no"
  39. @leadin="" - text that appears once at the start of the element. It does not get the
  40. extra indenting specified by @indent.
  41. Attributes on text:
  42. @style="bold|italics|underlined|tt|sup|sub"
  43. @href="" [target, if this is a link]
  44. @format="" copy through @format for a link
  45. @scope="" copy through @scope for a link
  46. -->
  47. <xsl:import href="plugin:org.dita.base:xsl/common/output-message.xsl"/>
  48. <xsl:import href="plugin:org.dita.base:xsl/common/dita-utilities.xsl"/>
  49. <xsl:output method="text"
  50. encoding="UTF-8"
  51. indent="no"
  52. omit-xml-declaration = "yes"
  53. />
  54. <xsl:param name="LINELENGTH">65</xsl:param>
  55. <xsl:param name="FILENAME"></xsl:param>
  56. <!-- Deprecated since 2.3 -->
  57. <xsl:variable name="msgprefix">DOTX</xsl:variable>
  58. <xsl:variable name="OUTEXT">txt</xsl:variable>
  59. <!-- Single newline character. This is used to search for newlines in pre-formatted text, and
  60. is used for wrapping text (processors may choose to reflow wrapped text). -->
  61. <xsl:variable name="newline"><xsl:text>
  62. </xsl:text></xsl:variable>
  63. <xsl:template name="force-newline">
  64. <xsl:value-of select="$newline"/>
  65. </xsl:template>
  66. <xsl:template name="force-two-newlines">
  67. <xsl:value-of select="$newline"/><xsl:value-of select="$newline"/>
  68. </xsl:template>
  69. <!-- Turn on centering -->
  70. <xsl:template name="start-centering">
  71. <xsl:value-of select="$newline"/>
  72. </xsl:template>
  73. <!-- Turn on centering -->
  74. <xsl:template name="stop-centering">
  75. <xsl:value-of select="$newline"/>
  76. </xsl:template>
  77. <!-- root rule -->
  78. <xsl:template match="/">
  79. <xsl:apply-templates select="*[1]"/>
  80. </xsl:template>
  81. <xsl:template match="*">
  82. <xsl:apply-templates select="*[1]"/>
  83. </xsl:template>
  84. <!-- Find the current indent length. If formatters (such as troff?) do indenting on
  85. their own, they can always return '' from this function. -->
  86. <xsl:template match="*" mode="find-indent">
  87. <xsl:choose>
  88. <xsl:when test="not(@indent) or @expanse='page'"/>
  89. <xsl:when test="@indent='1'"><xsl:text> </xsl:text></xsl:when>
  90. <xsl:when test="@indent='2'"><xsl:text> </xsl:text></xsl:when>
  91. <xsl:when test="@indent='3'"><xsl:text> </xsl:text></xsl:when>
  92. <xsl:when test="@indent='4'"><xsl:text> </xsl:text></xsl:when>
  93. <xsl:when test="@indent='5'"><xsl:text> </xsl:text></xsl:when>
  94. <xsl:when test="@indent='6'"><xsl:text> </xsl:text></xsl:when>
  95. <xsl:when test="@indent='7'"><xsl:text> </xsl:text></xsl:when>
  96. <xsl:when test="@indent='8'"><xsl:text> </xsl:text></xsl:when>
  97. <xsl:when test="@indent='9'"><xsl:text> </xsl:text></xsl:when>
  98. <xsl:when test="@indent='10'"><xsl:text> </xsl:text></xsl:when>
  99. <xsl:when test="@indent='11'"><xsl:text> </xsl:text></xsl:when>
  100. <xsl:when test="@indent='12'"><xsl:text> </xsl:text></xsl:when>
  101. <xsl:when test="@indent='13'"><xsl:text> </xsl:text></xsl:when>
  102. <xsl:when test="@indent='14'"><xsl:text> </xsl:text></xsl:when>
  103. <xsl:when test="@indent='15'"><xsl:text> </xsl:text></xsl:when>
  104. </xsl:choose>
  105. </xsl:template>
  106. <xsl:template name="getFirstWord">
  107. <xsl:param name="string"/>
  108. <xsl:choose>
  109. <!-- For DBCS text, only take one character, unless English, or followed by punctuation -->
  110. <xsl:when test="contains($string,' ')"><xsl:value-of select="substring-before($string,' ')"/></xsl:when>
  111. <xsl:otherwise><xsl:value-of select="$string"/></xsl:otherwise>
  112. </xsl:choose>
  113. </xsl:template>
  114. <!-- This drops leading spaces... but that's already done if calling with normalize-space.
  115. The function recursivly processes "string" while counting the line length. It adds a
  116. newline when needed, and resets the current length.
  117. If a formatter wants to control indenting, simply start with the indent command. May want
  118. to add a parameter that makes it easy to tell if this is the first time "wrap" was called
  119. in order to enable this. Then update the find-indent template to return '' for that output
  120. format. -->
  121. <xsl:template name="wrap">
  122. <xsl:param name="string"/>
  123. <xsl:param name="curLength" select="0"/>
  124. <xsl:param name="leadin"/> <!-- Text to use once, before indent -->
  125. <xsl:param name="addIndent">
  126. <xsl:choose>
  127. <xsl:when test="@expanse='page'"/> <!-- Ignore any active indent -->
  128. <!-- If there is lead-in text that does not indent, only get the indent from ancestor blocks -->
  129. <xsl:when test="string-length(normalize-space($leadin))>0">
  130. <xsl:apply-templates select="ancestor-or-self::block/ancestor::*[@indent]" mode="find-indent"/>
  131. </xsl:when>
  132. <!-- Otherwise, start with the indent on the current element -->
  133. <xsl:otherwise>
  134. <xsl:apply-templates select="ancestor-or-self::*[@indent]" mode="find-indent"/>
  135. </xsl:otherwise>
  136. </xsl:choose>
  137. </xsl:param>
  138. <xsl:variable name="firstword">
  139. <xsl:call-template name="getFirstWord">
  140. <xsl:with-param name="string" select="$string"/>
  141. </xsl:call-template>
  142. </xsl:variable>
  143. <xsl:variable name="remainder" select="substring-after($string,' ')"/>
  144. <xsl:choose>
  145. <!-- If there is leadin text, issue it with the current indent, before
  146. adding any indent for this block -->
  147. <xsl:when test="string-length(normalize-space($leadin))>0">
  148. <xsl:value-of select="$addIndent"/>
  149. <xsl:value-of select="$leadin"/>
  150. <xsl:value-of select="$firstword"/>
  151. <xsl:call-template name="wrap">
  152. <xsl:with-param name="string" select="$remainder"/>
  153. <xsl:with-param name="curLength" select="string-length($leadin) + string-length($firstword) + string-length($addIndent)"/>
  154. </xsl:call-template>
  155. </xsl:when>
  156. <!-- End of the string; nothing left to evaluate, so quit -->
  157. <xsl:when test="string-length($string)=0"/>
  158. <!-- At the start of the line; add the word, whatever the length -->
  159. <xsl:when test="$curLength = 0">
  160. <xsl:value-of select="$addIndent"/>
  161. <xsl:value-of select="$firstword"/>
  162. <xsl:call-template name="wrap">
  163. <xsl:with-param name="string" select="$remainder"/>
  164. <xsl:with-param name="curLength" select="string-length($firstword) + string-length($addIndent)"/>
  165. <xsl:with-param name="addIndent" select="$addIndent"/>
  166. </xsl:call-template>
  167. </xsl:when>
  168. <!-- Normal text. This word does not fit on the line. End this line, start the next. -->
  169. <xsl:when test="string-length($firstword) + 1 + number($curLength) > $LINELENGTH">
  170. <xsl:value-of select="$newline"/>
  171. <xsl:value-of select="$addIndent"/>
  172. <xsl:value-of select="$firstword"/>
  173. <xsl:call-template name="wrap">
  174. <xsl:with-param name="string" select="$remainder"/>
  175. <xsl:with-param name="curLength" select="string-length($firstword) + string-length($addIndent)"/>
  176. <xsl:with-param name="addIndent" select="$addIndent"/>
  177. </xsl:call-template>
  178. </xsl:when>
  179. <!-- Normal text; this word fits on the line. Add it and continue. -->
  180. <xsl:otherwise>
  181. <xsl:if test="$curLength>0"><xsl:text> </xsl:text></xsl:if>
  182. <xsl:value-of select="$firstword"/>
  183. <xsl:call-template name="wrap">
  184. <xsl:with-param name="string" select="$remainder"/>
  185. <xsl:with-param name="curLength" select="number($curLength) + 1 + string-length($firstword)"/>
  186. <xsl:with-param name="addIndent" select="$addIndent"/>
  187. </xsl:call-template>
  188. </xsl:otherwise>
  189. </xsl:choose>
  190. </xsl:template>
  191. <!-- Process pre-formatted text. Newlines should be preserved, none should be added. -->
  192. <xsl:template name="preserve-space">
  193. <xsl:param name="string" select="."/>
  194. <xsl:param name="leadin"/> <!-- Text to use once, before indent -->
  195. <xsl:param name="addIndent">
  196. <xsl:choose>
  197. <xsl:when test="@expanse='page'"/> <!-- Ignore any active indent -->
  198. <!-- If there is lead-in text that does not indent, only get the indent from ancestor blocks -->
  199. <xsl:when test="string-length(normalize-space($leadin))>0">
  200. <xsl:apply-templates select="ancestor-or-self::block/ancestor::*[@indent]" mode="find-indent"/>
  201. </xsl:when>
  202. <!-- Otherwise, start with the indent on the current element -->
  203. <xsl:otherwise>
  204. <xsl:apply-templates select="ancestor-or-self::*[@indent]" mode="find-indent"/>
  205. </xsl:otherwise>
  206. </xsl:choose>
  207. </xsl:param>
  208. <xsl:choose>
  209. <xsl:when test="string-length($string)=0"/>
  210. <xsl:when test="contains($string,$newline)">
  211. <!-- Warn if the line exceeds the limit? -->
  212. <xsl:value-of select="$addIndent"/>
  213. <xsl:value-of select="substring-before($string,$newline)"/>
  214. <xsl:call-template name="force-newline"/>
  215. <xsl:call-template name="preserve-space">
  216. <xsl:with-param name="string" select="substring-after($string,$newline)"/>
  217. </xsl:call-template>
  218. </xsl:when>
  219. <xsl:otherwise>
  220. <xsl:value-of select="$addIndent"/>
  221. <xsl:value-of select="$string"/>
  222. </xsl:otherwise>
  223. </xsl:choose>
  224. </xsl:template>
  225. <xsl:template name="center-this-block">
  226. <xsl:if test="@position='center' and not(ancestor::*[@position='center'])">
  227. <xsl:call-template name="start-centering"/>
  228. </xsl:if>
  229. </xsl:template>
  230. <xsl:template name="UN-center-this-block">
  231. <xsl:if test="@position='center' and not(ancestor::*[@position='center'])">
  232. <xsl:call-template name="stop-centering"/>
  233. </xsl:if>
  234. </xsl:template>
  235. <!-- Process a block. If there was a block or text immediately before, we need to jump down
  236. a new line. -->
  237. <!-- If a block is in <text> it probably means this was a breaking image in a phrase.
  238. Otherwise, blocks should not be able to appear in text. In that case, treat it as inline. -->
  239. <xsl:template match="block">
  240. <xsl:variable name="thisLeadin">
  241. <!-- If there is no text inside here, and it should have lead-in (such as a list
  242. number), ensure the lead-in still shows up. -->
  243. <xsl:if test="@leadin and (not(*) or *[1][self::block|section])"><xsl:value-of select="@leadin"/></xsl:if>
  244. </xsl:variable>
  245. <xsl:variable name="leadinWithIndent">
  246. <xsl:if test="normalize-space($thisLeadin)">
  247. <xsl:apply-templates select="ancestor::*[@indent]" mode="find-indent"/>
  248. <xsl:value-of select="$thisLeadin"/>
  249. <xsl:value-of select="$newline"/>
  250. </xsl:if>
  251. </xsl:variable>
  252. <xsl:choose>
  253. <xsl:when test="ancestor::text">
  254. <xsl:value-of select="$thisLeadin"/> <!-- If doing it inline, do not use indent -->
  255. <xsl:apply-templates select="*[1]"/>
  256. </xsl:when>
  257. <xsl:otherwise>
  258. <xsl:if test="preceding-sibling::*">
  259. <xsl:choose>
  260. <xsl:when test="@compact='yes'"><xsl:call-template name="force-newline"/></xsl:when>
  261. <xsl:otherwise><xsl:call-template name="force-two-newlines"/></xsl:otherwise>
  262. </xsl:choose>
  263. </xsl:if>
  264. <xsl:call-template name="center-this-block"/>
  265. <xsl:value-of select="$leadinWithIndent"/>
  266. <xsl:apply-templates select="*[1]"/>
  267. <xsl:call-template name="UN-center-this-block"/>
  268. </xsl:otherwise>
  269. </xsl:choose>
  270. <xsl:apply-templates select="following-sibling::*[1]"/>
  271. </xsl:template>
  272. <!-- If the section has a title, TROFF can use the .SH macro to get the title formatting. -->
  273. <xsl:template match="section">
  274. <xsl:choose>
  275. <xsl:when test="sectiontitle">
  276. <xsl:apply-templates select="sectiontitle[1]"/>
  277. <xsl:apply-templates select="(text|block)[1]"/>
  278. </xsl:when>
  279. <xsl:otherwise>
  280. <xsl:if test="preceding-sibling::*">
  281. <xsl:call-template name="force-two-newlines"/>
  282. </xsl:if>
  283. <xsl:apply-templates select="*[1]"/>
  284. </xsl:otherwise>
  285. </xsl:choose>
  286. <xsl:apply-templates select="following-sibling::*[1]"/>
  287. </xsl:template>
  288. <!-- Based on step1, section titles should come first in the section. If this is
  289. a *ROFF format, use the .SH macro to get roff's section-like formatting. -->
  290. <xsl:template match="sectiontitle">
  291. <xsl:if test="preceding-sibling::*">
  292. <xsl:call-template name="force-two-newlines"/>
  293. </xsl:if>
  294. <xsl:call-template name="force-two-newlines"/>
  295. <xsl:apply-templates select="*[1]"/>
  296. <!-- Do not process following siblings: those come through from section -->
  297. </xsl:template>
  298. <!-- Use <block @position="center"> for centering - not yet implemented.
  299. This template matches pre-formatted blocks like <pre> and <lines>.
  300. May be able to update this to use TROFF commands that create an entire
  301. preformatted section; would need to make sure that nested elements do
  302. not cause problems with that. -->
  303. <xsl:template match="block[@xml:space='preserve']">
  304. <xsl:variable name="thisLeadin">
  305. <!-- If there is no text inside here, and it should have lead-in (such as a list
  306. number), ensure the lead-in still shows up. -->
  307. <xsl:if test="@leadin and (not(*) or *[1][self::block|section])"><xsl:value-of select="@leadin"/></xsl:if>
  308. </xsl:variable>
  309. <xsl:variable name="leadinWithIndent">
  310. <xsl:if test="normalize-space($thisLeadin)">
  311. <xsl:apply-templates select="ancestor::*[@indent]" mode="find-indent"/>
  312. <xsl:value-of select="$thisLeadin"/>
  313. <xsl:value-of select="$newline"/>
  314. </xsl:if>
  315. </xsl:variable>
  316. <xsl:choose>
  317. <xsl:when test="ancestor::text"> <!-- Should not ever be active, but just in case -->
  318. <xsl:value-of select="$thisLeadin"/>
  319. <xsl:call-template name="preserve-space"/>
  320. </xsl:when>
  321. <xsl:otherwise>
  322. <xsl:if test="preceding-sibling::*">
  323. <xsl:call-template name="force-two-newlines"/>
  324. </xsl:if>
  325. <xsl:call-template name="center-this-block"/>
  326. <xsl:value-of select="$leadinWithIndent"/>
  327. <xsl:call-template name="preserve-space"/>
  328. <xsl:call-template name="UN-center-this-block"/>
  329. </xsl:otherwise>
  330. </xsl:choose>
  331. <xsl:apply-templates select="(following-sibling::*)[1]"/>
  332. </xsl:template>
  333. <!-- This is called to process the contents of <text> elements. It will set
  334. the correct style if needed, and process children, and then return the
  335. style to normal. -->
  336. <xsl:template name="format-text">
  337. <xsl:param name="current-style" select="'normal'"/>
  338. <xsl:apply-templates select="." mode="format-text">
  339. <xsl:with-param name="current-style" select="@style"/>
  340. </xsl:apply-templates>
  341. </xsl:template>
  342. <xsl:template match="*" mode="format-text">
  343. <xsl:param name="current-style" select="'normal'"/>
  344. <xsl:apply-templates>
  345. <xsl:with-param name="current-style" select="@style"/>
  346. </xsl:apply-templates>
  347. </xsl:template>
  348. <!-- For text within text, do not worry about newlines. Just process the contents. -->
  349. <xsl:template match="text/text">
  350. <xsl:param name="current-style"/>
  351. <xsl:call-template name="format-text">
  352. <xsl:with-param name="current-style" select="$current-style"/>
  353. </xsl:call-template>
  354. </xsl:template>
  355. <!-- For text that is directly inside a block or section, newlines may be needed. Process
  356. all consecutive text elements at once. -->
  357. <xsl:template match="text">
  358. <!-- There should not be a style active, because the parent is a block. However, in the future,
  359. there could be a reason to set an entire block to bold, italics, etc, so go ahead
  360. and allow for it. -->
  361. <xsl:param name="current-style"/>
  362. <!-- Get all of the text up to the next block. This allows for easy wrapping, and prevents
  363. us from putting out newlines between text elements. First get the current element,
  364. then progress through following elements. -->
  365. <xsl:variable name="upToBlock">
  366. <xsl:call-template name="format-text">
  367. <xsl:with-param name="current-style" select="$current-style"/>
  368. </xsl:call-template>
  369. <xsl:apply-templates select="(following-sibling::*)[1]" mode="text-in-block">
  370. <xsl:with-param name="current-style" select="$current-style"/>
  371. </xsl:apply-templates>
  372. </xsl:variable>
  373. <!-- If text comes after </block>, jump to the next line, then leave a blank line. -->
  374. <xsl:if test="(preceding-sibling::*)[last()][self::block|self::section|self::sectiontitle]">
  375. <xsl:call-template name="force-two-newlines"/>
  376. </xsl:if>
  377. <!-- Process all text up to the next block. If the parent block had lead-in text, and
  378. it has not been used, pass that to the function. -->
  379. <xsl:call-template name="wrap">
  380. <xsl:with-param name="string" select="normalize-space($upToBlock)"/>
  381. <xsl:with-param name="leadin">
  382. <xsl:if test="../@leadin">
  383. <xsl:if test="not(preceding-sibling::*)"><xsl:value-of select="../@leadin"/></xsl:if>
  384. </xsl:if>
  385. </xsl:with-param>
  386. </xsl:call-template>
  387. <xsl:apply-templates select="(following-sibling::block|following-sibling::section)[1]"/>
  388. </xsl:template>
  389. <!-- This matches text when sequentially moving through text blocks. Process
  390. the contents, and move on to the next element. -->
  391. <xsl:template match="text" mode="text-in-block">
  392. <xsl:param name="current-style"/>
  393. <xsl:call-template name="format-text">
  394. <xsl:with-param name="current-style" select="$current-style"/>
  395. </xsl:call-template>
  396. <xsl:apply-templates select="following-sibling::*[1]" mode="text-in-block"/>
  397. </xsl:template>
  398. <!-- When moving through text elements, stop at block or section. -->
  399. <xsl:template match="block|section" mode="text-in-block"/>
  400. </xsl:stylesheet>