├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── ht-latex ├── html-plain.cfg ├── post-process-html.py ├── run-tests.py └── tests ├── citation-001 ├── expected.html ├── references.bib └── test.tex ├── citation-002 ├── expected.html ├── references.bib ├── result-test. └── test.tex ├── citation-003 ├── expected.html ├── references.bib └── test.tex ├── citation-004 ├── ACM-Reference-Format.bst ├── acmart.cls ├── expected.html ├── references.bib └── test.tex ├── footnote-001 ├── expected.html └── test.tex ├── header-abstract-001 ├── expected.html └── test.tex ├── ligatures-001 ├── expected.html └── test.tex ├── listing-001 ├── expected.html └── test.tex ├── listing-002 ├── expected.html └── test.tex ├── resources ├── ACM-Reference-Format.bst └── acmart.cls ├── sections-001 ├── expected.html ├── references.bib └── test.tex └── special-chars-001 ├── expected.html └── test.tex /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-and-test: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: texlive/texlive:latest 10 | steps: 11 | - name: Set up Git repository 12 | uses: actions/checkout@v3 13 | 14 | - name: Install Dependencies 15 | run: | 16 | apt-get update 17 | apt-get install -y tidy python3-pip 18 | python -m pip install --break-system-packages beautifulsoup4 html5lib 19 | 20 | - name: Run Tests 21 | run: | 22 | python run-tests.py 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test output files 2 | test.4ct 3 | test-final.html 4 | test-tidy.html 5 | test.4tc 6 | test.css 7 | test.dvi 8 | test.html 9 | test.idv 10 | test.html 11 | test.lg 12 | test.tmp 13 | test.xref 14 | test.pdf 15 | test.xcp 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LaTeX to HTML5 2 | ============== 3 | 4 | This repository contains configuration files for tex4ht and post processing 5 | scripts to customize and simplify the HTML generated by tex4ht. Instead of 6 | preserving the full generality of LaTeX, it seems to be a better approach for 7 | the Web to concentrate on the semantic elements and provide a suitable CSS file. 8 | 9 | 10 | ### Requirements 11 | 12 | - tex4ht aka htlatex 13 | - tidy, see https://www.html-tidy.org/ 14 | - Python, and packages via `pip install` 15 | - BeautifulSoup4 16 | - html5lib 17 | 18 | ### Usage 19 | 20 | ``` 21 | $ ht-latex tex-file output-dir 22 | ``` 23 | 24 | The output file is going to be `tex-file-final.html`. 25 | 26 | ### Examples 27 | 28 | - http://stefan-marr.de/papers/pldi-marr-et-al-zero-overhead-metaprogramming/ 29 | - http://stefan-marr.de/papers/oopsla-marr-ducasse-meta-tracing-vs-partial-evaluation/ 30 | 31 | ### Status and Contributions 32 | 33 | The current status of this project is: *highly experimental and optimized for 34 | myself*, ah, and of course, it works on my machine... 35 | 36 | Pull requests to improve the situation are very welcome. 37 | 38 | ### Tests 39 | 40 | There are a couple of basic tests in `tests`, which can be executed with 41 | `run-tests.py`. 42 | 43 | ### License 44 | 45 | This project is licensed under the MIT license: https://opensource.org/licenses/MIT 46 | -------------------------------------------------------------------------------- /ht-latex: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # set -x 3 | 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | MAIN_FILE="${1%.tex}" 7 | OUTPUT_FOLDER="$2" 8 | HTML_SETTINGS="${SCRIPT_DIR}/html-plain,fn-in,charset=utf-8" 9 | ENCODING="-cunihtf -utf8" 10 | 11 | mkdir -p $OUTPUT_FOLDER 12 | 13 | echo Pre-Build With PDF Latex 14 | pdflatex $MAIN_FILE || true 15 | bibtex $MAIN_FILE 16 | pdflatex $MAIN_FILE || true 17 | pdflatex $MAIN_FILE 18 | 19 | echo 20 | echo Build With Latex and TEX4HT 21 | latex $5 '\makeatletter\def\HCode{\futurelet\HCode\HChar}\def\HChar{\ifx"\HCode\def\HCode"##1"{\Link##1}\expandafter\HCode\else\expandafter\Link\fi}\def\Link#1.a.b.c.{\g@addto@macro\@documentclasshook{\RequirePackage[#1,html]{tex4ht}}\let\HCode\documentstyle\def\documentstyle{\let\documentstyle\HCode\expandafter\def\csname tex4ht\endcsname{#1,html}\def\HCode####1{\documentstyle[tex4ht,}\@ifnextchar[{\HCode}{\documentstyle[tex4ht]}}}\makeatother\HCode '$HTML_SETTINGS'.a.b.c.\input ' $MAIN_FILE 22 | #latex $5 '\makeatletter\def\HCode{\futurelet\HCode\HChar}\def\HChar{\ifx"\HCode\def\HCode"##1"{\Link##1}\expandafter\HCode\else\expandafter\Link\fi}\def\Link#1.a.b.c.{\g@addto@macro\@documentclasshook{\RequirePackage[#1,html]{tex4ht}}\let\HCode\documentstyle\def\documentstyle{\let\documentstyle\HCode\expandafter\def\csname tex4ht\endcsname{#1,html}\def\HCode####1{\documentstyle[tex4ht,}\@ifnextchar[{\HCode}{\documentstyle[tex4ht]}}}\makeatother\HCode '$HTML_SETTINGS'.a.b.c.\input ' $MAIN_FILE 23 | #latex $5 '\makeatletter\def\HCode{\futurelet\HCode\HChar}\def\HChar{\ifx"\HCode\def\HCode"##1"{\Link##1}\expandafter\HCode\else\expandafter\Link\fi}\def\Link#1.a.b.c.{\g@addto@macro\@documentclasshook{\RequirePackage[#1,html]{tex4ht}}\let\HCode\documentstyle\def\documentstyle{\let\documentstyle\HCode\expandafter\def\csname tex4ht\endcsname{#1,html}\def\HCode####1{\documentstyle[tex4ht,}\@ifnextchar[{\HCode}{\documentstyle[tex4ht]}}}\makeatother\HCode '$HTML_SETTINGS'.a.b.c.\input ' $MAIN_FILE 24 | 25 | echo 26 | echo Do Tex4HT 27 | tex4ht -f/$MAIN_FILE -i~/tex4ht.dir/texmf/tex4ht/ht-fonts/ $ENCODING 28 | t4ht -f/$MAIN_FILE -d$OUTPUT_FOLDER 29 | 30 | echo 31 | echo Run Tidy 32 | cp $MAIN_FILE.html $OUTPUT_FOLDER/ 33 | tidy -ashtml -utf8 $OUTPUT_FOLDER/$MAIN_FILE.html > $OUTPUT_FOLDER/$MAIN_FILE-tidy.html || true 34 | 35 | echo Run Post Process HTML 36 | export PYTHONWARNINGS=ignore 37 | $SCRIPT_DIR/post-process-html.py $OUTPUT_FOLDER/$MAIN_FILE-tidy.html > $OUTPUT_FOLDER/$MAIN_FILE-final.html 38 | -------------------------------------------------------------------------------- /html-plain.cfg: -------------------------------------------------------------------------------- 1 | \Preamble{xhtml} 2 | 3 | \RequirePackage{ifthen} 4 | 5 | \ifthenelse{\isundefined{\citeurl}}{}{ 6 | \renewcommand{\citeurl}[5]{\HCode{}#1\HCode{}} 7 | } 8 | 9 | % \renewcommand{\code}[1]{\lstinline!#1!} 10 | % \renewcommand{\java}[1]{\code{#1}} 11 | % \renewcommand{\py}[1]{\code{#1}} 12 | % \renewcommand{\stcode}[1]{\code{#1}} 13 | % \renewcommand{\rbcode}[1]{\code{#1}} 14 | 15 | % \def{\code}[1]{\lstinline!#1!} 16 | % \def{\java}[1]{\code{#1}} 17 | % \def{\py}[1]{\code{#1}} 18 | % \def{\stcode}[1]{\code{#1}} 19 | % \def{\rbcode}[1]{\code{#1}} 20 | 21 | \renewcommand{\textit}[1]{\HCode{}#1\HCode{}} 22 | \renewcommand{\textbf}[1]{\HCode{}#1\HCode{}} 23 | \renewcommand{\emph}[1]{\HCode{}#1\HCode{}} 24 | \renewcommand{\texttt}[1]{\HCode{}#1\HCode{}} 25 | \renewcommand{\textbackslash}{\HCode{!!TEXTBACKSLASH!!}} 26 | 27 | \Configure{()}{\HCode{}}{\HCode{}} 28 | % \Configure{footnote}{\HCode{FOT1}}{\HCode{FOT2}} 29 | \Configure{footnote}{}{}{}{} 30 | \Configure{footnotetext}{\HCode{}}{}{\HCode{}} 31 | \Configure{footnotemark}{}{} 32 | 33 | 34 | \Configure{section}{\HCode{
}}{\HCode{
}}{\HCode{

}\arabic{section}\HCode{   }}{\HCode{

}} 35 | \Configure{subsection}{}{}{\HCode{

}\arabic{section}.\arabic{subsection}\HCode{   }}{\HCode{

}} 36 | \Configure{HtmlPar}{\HCode{

}}{\EndP\HCode{

}}{\HCode{

}}{\HCode{

}} 37 | \Configure{caption}{\HCode{
}}{:\HCode{} }{}{\HCode{
}} 38 | \ConfigureEnv{figure}{\EndP\HCode{
}}{\HCode{
}}{}{} 39 | \Configure{float}{}{}{}{} 40 | 41 | \makeatletter 42 | \ifthenelse{\isundefined{\knitrForHt}}{}{ % this is brittle, but oh well 43 | \renewenvironment{knitrout}{ 44 | \renewcommand{\@iinput}[1]{TODO: convert to image: {##1}}% 45 | }{} 46 | } 47 | \makeatother 48 | 49 | \ifthenelse{\isundefined{\lstinline}}{}{ 50 | % else 51 | \Configure{lstinline}{\HCode{}}{\HCode{}} 52 | \ConfigureEnv{lstlisting} 53 | {\ifvmode \IgnorePar\fi \EndP 54 | \gHAdvance\listingN by 1 55 | \gdef\start:LstLn{% 56 | \HCode{
}%
57 |        \gdef\start:LstLn{\leavevmode\special{t4ht@+\string&{35}x000A{59}}x}}%%% this special string thing inserts an HTML entity for a newline... didn't find another way
58 |     \bgroup
59 |        \Configure{listings}
60 |          {{\everypar{}\leavevmode}}
61 |          {{\everypar{}\leavevmode}}
62 |          {\start:LstLn }
63 |          {}%
64 |    }
65 |    {\egroup
66 |     \ifvmode \IgnorePar\fi \EndP \HCode{
}\par} 67 | {} {} 68 | % done configuring listing 69 | } 70 | 71 | \begin{document} 72 | 73 | \EndPreamble 74 | -------------------------------------------------------------------------------- /post-process-html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import sys 4 | from bs4 import BeautifulSoup, Comment, Doctype, Tag, NavigableString 5 | 6 | # soup = BeautifulSoup(open(sys.argv[1]), "html.parser") 7 | soup = BeautifulSoup(open(sys.argv[1]), "html5lib") 8 | 9 | 10 | def combine_citation_links(soup): 11 | for e in soup.find_all("span", attrs={"class": "cite"}): 12 | # find_all will create a list, that also contains stuff 13 | # we already processed, i.e., without parent 14 | if e.parent is None: 15 | continue 16 | 17 | if e.previous_sibling == ',\xa0': 18 | # within a list, use normal spaces to avoid typesetting issues 19 | e.previous_sibling.replace_with(", ") 20 | 21 | a_elem = e.next_element 22 | 23 | # also ignore just year links, we might just have fixed the space before 24 | if a_elem.contents[0].isnumeric(): 25 | # though, we should still unwrap them 26 | e.replace_with(a_elem) 27 | continue 28 | 29 | # Process text like 'Author [Year]' 30 | if (e.next_sibling == '\xa0[' and e.next_sibling and 31 | e.next_sibling.next_sibling and 32 | e.next_sibling.next_sibling.next_sibling): 33 | space_bracket = e.next_sibling 34 | year_node_span = e.next_sibling.next_sibling 35 | year_node_a = year_node_span.next_element 36 | following_bracket_and_more = e.next_sibling.next_sibling.next_sibling 37 | 38 | if str(following_bracket_and_more)[0] == ']': 39 | assert a_elem.attrs['href'] == year_node_a.attrs['href'],\ 40 | "should be always the same, because we want to merge those two tags" 41 | year = str(year_node_a.contents[0]) 42 | a_elem.contents[0].replace_with(a_elem.contents[0] + '\xa0[' + year + "]") 43 | space_bracket.extract() 44 | year_node_span.extract() 45 | year_node_a.extract() 46 | 47 | # remove closing bracket 48 | following_bracket_and_more.replace_with( 49 | str(following_bracket_and_more)[1:]) 50 | e.replace_with(a_elem) 51 | continue 52 | 53 | # transform 'Author [Year, Year2]' 54 | e.replace_with(a_elem.contents[0]) # remove link from author 55 | continue 56 | 57 | # Process text like '[AuthorYear]' 58 | # This also includes situations with multiple references, that are 59 | # listed with author+year. 60 | if (e.next_sibling == ',\xa0' and 61 | isinstance(e.next_sibling.next_sibling, Tag)): 62 | possible_span = e.next_sibling.next_sibling 63 | if possible_span.name == 'span' and possible_span.attrs.get('class') == ['cite']: 64 | a_elem2 = possible_span.next_element 65 | if a_elem2.name == 'a' and a_elem2.attrs.get('href') == a_elem.attrs['href']: 66 | comma_space_node = e.next_sibling 67 | year_node_span = e.next_sibling.next_sibling 68 | year_node_a = year_node_span.next_element 69 | year_node = year_node_a.contents[0] 70 | 71 | if (year_node_a.next_sibling and 72 | year_node_a.next_sibling.next_sibling and 73 | year_node_a.next_sibling.next_sibling.name == "a" and 74 | year_node_a.next_sibling.next_sibling.contents[0].isnumeric()): 75 | # looks like we got multiple years with the same author in 76 | # succession, remove the link on the author to avoid confusion 77 | prev = e.previous_sibling 78 | if prev == ',\xa0': 79 | prev.replace_with(", ") 80 | elif (isinstance(prev, NavigableString) and 81 | str(prev)[-1] == "["): 82 | prev.replace_with(s[:-2] + "\N{THIN SPACE}[") 83 | e.replace_with(e.contents[0]) 84 | 85 | else: 86 | comma_space_node.extract() 87 | year_node_span.extract() 88 | a_elem.append(comma_space_node) 89 | a_elem.append(year_node) 90 | 91 | # if we are the first in the list, make sure the bracket is 92 | # directly connected with a   to the element before 93 | if isinstance(e.previous_sibling, NavigableString): 94 | s = str(e.previous_sibling) 95 | if s[-1] == "[" and s[-2].isspace(): 96 | e.previous_sibling.replace_with(s[:-2] + "\N{THIN SPACE}[") 97 | 98 | # at the end, unwrap the from the , we don't really need it 99 | e.replace_with(a_elem) 100 | 101 | 102 | def remove_font_spans(soup): 103 | for e in soup.find_all("span", {'class' : re.compile("^LinLibertine") }): 104 | e.replace_with(e.contents[0]) 105 | for e in soup.find_all("span", {'class' : re.compile("^LinBiolinum") }): 106 | e.replace_with(e.contents[0]) 107 | for e in soup.find_all("span", {'class' : re.compile("^cm") }): 108 | e.replace_with(e.contents[0]) 109 | for e in soup.find_all("span", {'class' : re.compile("^pcrr") }): 110 | e.replace_with(e.contents[0]) 111 | for e in soup.find_all("span", {'class' : re.compile("^ptmr") }): 112 | e.replace_with(e.contents[0]) 113 | 114 | 115 | def remove_following_newline(e): 116 | sibling = e.next_sibling 117 | if sibling.string == '\n': 118 | sibling.extract() 119 | 120 | 121 | def remove_unwanted_meta(soup): 122 | for e in soup.find_all("meta"): 123 | if e.has_attr('name') and e['name'] in ['date', 'src', 'originator', 124 | 'generator']: 125 | remove_following_newline(e) 126 | e.extract() 127 | 128 | 129 | def remove_tex4ht_comments(soup): 130 | for e in soup.find_all(string=lambda text:isinstance(text, Doctype)): 131 | remove_following_newline(e) 132 | e.extract() 133 | 134 | for e in soup.find_all(string=lambda text:isinstance(text, Comment)): 135 | if 'fn-in,' in e.string or e.string.startswith('?xml') or e.string.startswith('http://www.w3.org/TR/xhtml1'): 136 | remove_following_newline(e) 137 | e.extract() 138 | else: 139 | print(e) 140 | 141 | 142 | def remove_superfluous_id_after_footnote(soup): 143 | for e in soup.find_all("sup", {'class' : 'textsuperscript'}): 144 | sib = e.next_sibling 145 | if sib.name == "a" and sib.has_attr('id') and sib.has_attr('name') and sib['id'] == sib['name']: 146 | sib.extract() 147 | 148 | 149 | def transform_header(soap): 150 | head = soup.find("div", {"class": "maketitle"}) 151 | if not head or head.contents[1].name != "h2" or head.contents[3].name != "div": 152 | return # the format of the header is not currently supported 153 | 154 | if head.contents[0] == "\n": 155 | head.contents[0].extract() 156 | title_h2 = head.contents[0].extract() 157 | title = title_h2.getText() 158 | 159 | if head.contents[0] == "\n": 160 | head.contents[0].extract() 161 | author_div = head.contents[0].extract() 162 | authors = author_div.string 163 | 164 | title_h1 = soup.new_tag("h1") 165 | title_h1.string = title 166 | 167 | title_tag = soup.find("title") 168 | title_tag.string = title 169 | meta_author = soup.new_tag("meta") 170 | meta_author.attrs['name'] = "author" 171 | meta_author.attrs['content'] = authors 172 | 173 | head_tag = soup.find("head") 174 | head_tag.append(meta_author) 175 | 176 | header = soup.new_tag("header") 177 | header.append(title_h1) 178 | header.append(author_div) 179 | 180 | while len(head.contents) > 0: 181 | e = head.contents[0].extract() 182 | header.append(e) 183 | head.replace_with(header) 184 | 185 | # move abstract, if available 186 | abstract = soup.find("div", {"class": "abstract"}) 187 | if abstract: 188 | header.append(abstract.extract()) 189 | ab_t = abstract.find("div", {"class": "center"}) 190 | if ab_t and ab_t.p.string == "Abstract": 191 | ab_t.extract() 192 | ab_t = soup.new_tag("h3") 193 | ab_t.string = "Abstract" 194 | abstract.insert(0, ab_t) 195 | 196 | 197 | def wrap_body_in_article(soup): 198 | body = soup.find("body") 199 | article = soup.new_tag("article") 200 | # can't loop over .contents directly because it is changing 201 | while len(body.contents) > 0: 202 | article.append(body.contents[0].extract()) 203 | body.append(article) 204 | 205 | 206 | def add_line_numbers_to_listings(soup): 207 | for f in soup.find_all("figure"): 208 | div_ln = f.find("div", {"class": "linenumbers"}) 209 | if div_ln: 210 | code = f.find("code") 211 | lines = code.find_all("a") # find by labels, might be brittle 212 | ln_nums = " ".join([str(i) for i in range(1, len(lines) + 1)]) 213 | div_ln.string = ln_nums 214 | 215 | 216 | def in_code_remove_span_making_text_black(soup): 217 | for c in soup.find_all('code'): 218 | for e in c.find_all("span", attrs={"style": "color:#000000"}): 219 | e.replace_with(e.next_element) 220 | 221 | 222 | def remove_url_class(soup): 223 | for a in soup.find_all("a", {"class": "url"}): 224 | del a.attrs['class'] 225 | 226 | 227 | wrap_body_in_article(soup) 228 | remove_tex4ht_comments(soup) 229 | remove_unwanted_meta(soup) 230 | 231 | remove_superfluous_id_after_footnote(soup) 232 | combine_citation_links(soup) 233 | remove_font_spans(soup) 234 | transform_header(soup) 235 | add_line_numbers_to_listings(soup) 236 | in_code_remove_span_making_text_black(soup) 237 | remove_url_class(soup) 238 | 239 | result = soup.encode(encoding="utf-8", formatter="html").decode() 240 | 241 | result = result.replace('ffi', 'ffi') 242 | result = result.replace('ff', 'ff') 243 | result = result.replace('ffl', 'ffl') 244 | result = result.replace('fj', 'fj') 245 | result = result.replace('fl', 'fl') 246 | result = result.replace('fi', 'fi') 247 | result = result.replace('!!TEXTBACKSLASH!!', '\\') 248 | 249 | print(result) 250 | -------------------------------------------------------------------------------- /run-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | DEV_NULL = open(os.devnull, 'w') 7 | 8 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 9 | 10 | def check_delta_available(): 11 | """Delta is a nice tool for nicer diffs""" 12 | try: 13 | ret_code = subprocess.call(['delta', '--version'], stdout=DEV_NULL) 14 | return ret_code == 0 15 | except: 16 | return False 17 | 18 | 19 | HAS_DELTA = check_delta_available() 20 | 21 | 22 | def cleanup_directory(d): 23 | subprocess.call(['rm', '-f', 'test.aux', 'test.css', 'test.fls', 'test.lg', 24 | 'test.4ct', 'test.bbl', 'test.dvi', 'test.html', 25 | 'test.log', 'test.tmp', 'test.4tc', 'test.blg', 26 | 'test.fdb_latexmk', 'test.idv', 'test.pdf', 27 | 'test.xref', 'test-final.html', 'test-tidy.html', 28 | '.test.lb']) 29 | 30 | 31 | def exec_test(d): 32 | try: 33 | os.chdir(d) 34 | except FileNotFoundError as e: 35 | print(d, 'does not seem to exist') 36 | return False 37 | try: 38 | subprocess.check_output([BASE_DIR + '/ht-latex', 'test.tex', '.'], stderr=subprocess.STDOUT) 39 | try: 40 | if HAS_DELTA: 41 | diff_cmd = ['delta', '--syntax-theme', 'Solarized (light)'] 42 | else: 43 | diff_cmd = ['diff'] 44 | 45 | subprocess.check_output(diff_cmd + ['expected.html', 'test-final.html']) 46 | except subprocess.CalledProcessError as e: 47 | print(d, 'FAILED') 48 | print("Diff between expected and actual HTML:") 49 | print(e.output.decode('utf-8')) 50 | return False 51 | 52 | cleanup_directory(d) 53 | return True 54 | except subprocess.CalledProcessError as e: 55 | print(d, 'FAILED') 56 | print("Latex Output:") 57 | print(e.output.decode('utf-8')) 58 | return False 59 | 60 | 61 | def is_test(f): 62 | if not os.path.isdir(f): 63 | return False 64 | return os.path.isfile(f + '/test.tex') 65 | 66 | 67 | def exec_tests(): 68 | all_pass = True 69 | 70 | for f in sorted(os.listdir('tests')): 71 | os.chdir(BASE_DIR) 72 | if is_test('tests/' + f): 73 | print("-------", f) 74 | if not exec_test('tests/' + f): 75 | all_pass = False 76 | return all_pass 77 | 78 | 79 | if len(sys.argv) > 1: 80 | result = exec_test(sys.argv[1]) 81 | else: 82 | result = exec_tests() 83 | 84 | if result: 85 | sys.exit(0) 86 | else: 87 | sys.exit(1) 88 | -------------------------------------------------------------------------------- /tests/citation-001/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

main text citet example: LastName [1900] main text

8 |

main text(don’t forget the half-space, citep:) ctanchor [LastName, 1900]. text main.

9 |

References

10 |
11 |

   F. LastName. 12 | TitleTag. PublisherTag, AddressTag, 1900. ISBN 13 | IsbnTag.

14 |
15 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /tests/citation-001/references.bib: -------------------------------------------------------------------------------- 1 | @book{Test:Book, 2 | address = {AddressTag}, 3 | author = {LastName, FirstName}, 4 | isbn = {IsbnTag}, 5 | publisher = {PublisherTag}, 6 | title = {TitleTag}, 7 | year = 1900 8 | } 9 | -------------------------------------------------------------------------------- /tests/citation-001/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage[square,sort,comma,authoryear]{natbib} 4 | 5 | \usepackage{url} % format URLs 6 | 7 | \begin{document} 8 | 9 | main text citet example: \citet{Test:Book} main text 10 | 11 | main text(don't forget the half-space, citep:) ctanchor\,\citep{Test:Book}. 12 | text main. 13 | 14 | \bibliographystyle{abbrvnat} 15 | \bibliography{references} 16 | 17 | \end{document} 18 | -------------------------------------------------------------------------------- /tests/citation-002/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

citet example: LastName [1900], LastName1 8 | et al. [2005, 2013].

9 |

ctanchor [LastName, 1900, LastName1 10 | et al., 2005, 2013].

11 |

citet example: LastName [1900].

12 |

ctanchor [LastName, 1900].

13 |

citet example: LastName [1900], LastName1 14 | et al. [2005].

15 |

ctanchor [LastName1 16 | et al., 2005, 2013].

17 |

References

18 |
19 |

   F. LastName. 20 | TitleTag. PublisherTag, AddressTag, 1900. ISBN 21 | IsbnTag.

22 |

   F. LastName1, 23 | F. LastName2, and F. LastName3. Title. In 24 | BookTitle, Series, pages 439–453. ACM, 2005. ISBN Isbn. 25 | doi: Doi.

26 |

   F. LastName1, 27 | F. LastName2, and F. LastName3. Title. Journal, 28 | 45(4): 1–4, Aug. 2013. ISSN Issn. doi: Doi. URL http://url.

29 |
30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /tests/citation-002/references.bib: -------------------------------------------------------------------------------- 1 | @book{Test:Book, 2 | address = {AddressTag}, 3 | author = {LastName, FirstName}, 4 | isbn = {IsbnTag}, 5 | publisher = {PublisherTag}, 6 | title = {TitleTag}, 7 | year = 1900 8 | } 9 | 10 | @inproceedings{Test:Inproceedings, 11 | author = {LastName1, FirstName1 and LastName2, FirstName2 and LastName3, FirstName3}, 12 | booktitle = {BookTitle}, 13 | doi = {Doi}, 14 | isbn = {Isbn}, 15 | location = {Location}, 16 | numpages = {15}, 17 | pages = {439--453}, 18 | publisher = {ACM}, 19 | series = {Series}, 20 | title = {Title}, 21 | year = 2005 22 | } 23 | 24 | @article{Test:Article, 25 | address = {Address}, 26 | author = {LastName1, FirstName1 and LastName2, FirstName2 and LastName3, FirstName3}, 27 | doi = {Doi}, 28 | issn = {Issn}, 29 | journal = {Journal}, 30 | month = aug, 31 | number = 4, 32 | numpages = {34}, 33 | pages = {1--4}, 34 | publisher = {ACM}, 35 | title = {Title}, 36 | url = {http://url}, 37 | volume = 45, 38 | year = 2013 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/citation-002/result-test.: -------------------------------------------------------------------------------- 1 | 2 | /* start css.sty */ 3 | .cmtt-10{font-family: monospace;} 4 | .cmti-10{ font-style: italic;} 5 | p.noindent { text-indent: 0em } 6 | td p.noindent { text-indent: 0em; margin-top:0em; } 7 | p.nopar { text-indent: 0em; } 8 | p.indent{ text-indent: 1.5em } 9 | @media print {div.crosslinks {visibility:hidden;}} 10 | a img { border-top: 0; border-left: 0; border-right: 0; } 11 | center { margin-top:1em; margin-bottom:1em; } 12 | td center { margin-top:0em; margin-bottom:0em; } 13 | .Canvas { position:relative; } 14 | img.math{vertical-align:middle;} 15 | li p.indent { text-indent: 0em } 16 | li p:first-child{ margin-top:0em; } 17 | li p:last-child, li div:last-child { margin-bottom:0.5em; } 18 | li p~ul:last-child, li p~ol:last-child{ margin-bottom:0.5em; } 19 | .enumerate1 {list-style-type:decimal;} 20 | .enumerate2 {list-style-type:lower-alpha;} 21 | .enumerate3 {list-style-type:lower-roman;} 22 | .enumerate4 {list-style-type:upper-alpha;} 23 | div.newtheorem { margin-bottom: 2em; margin-top: 2em;} 24 | .obeylines-h,.obeylines-v {white-space: nowrap; } 25 | div.obeylines-v p { margin-top:0; margin-bottom:0; } 26 | .overline{ text-decoration:overline; } 27 | .overline img{ border-top: 1px solid black; } 28 | td.displaylines {text-align:center; white-space:nowrap;} 29 | .centerline {text-align:center;} 30 | .rightline {text-align:right;} 31 | div.verbatim {font-family: monospace; white-space: nowrap; text-align:left; clear:both; } 32 | .fbox {padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } 33 | div.fbox {display:table} 34 | div.center div.fbox {text-align:center; clear:both; padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } 35 | div.minipage{width:100%;} 36 | div.center, div.center div.center {text-align: center; margin-left:1em; margin-right:1em;} 37 | div.center div {text-align: left;} 38 | div.flushright, div.flushright div.flushright {text-align: right;} 39 | div.flushright div {text-align: left;} 40 | div.flushleft {text-align: left;} 41 | .underline{ text-decoration:underline; } 42 | .underline img{ border-bottom: 1px solid black; margin-bottom:1pt; } 43 | .framebox-c, .framebox-l, .framebox-r { padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } 44 | .framebox-c {text-align:center;} 45 | .framebox-l {text-align:left;} 46 | .framebox-r {text-align:right;} 47 | span.thank-mark{ vertical-align: super } 48 | span.footnote-mark sup.textsuperscript, span.footnote-mark a sup.textsuperscript{ font-size:80%; } 49 | div.footnotes{border-top:solid 1px black; border-bottom:solid 1px black; padding-bottom:1ex; padding-top:0.5ex; margin-right:15%; margin-top:2ex; font-style:italic; font-size:85%;} 50 | div.footnotes p{margin-top:0; margin-bottom:0; text-indent:0;} 51 | div.tabular, div.center div.tabular {text-align: center; margin-top:0.5em; margin-bottom:0.5em; } 52 | table.tabular td p{margin-top:0em;} 53 | table.tabular {margin-left: auto; margin-right: auto;} 54 | td p:first-child{ margin-top:0em; } 55 | td p:last-child{ margin-bottom:0em; } 56 | div.td00{ margin-left:0pt; margin-right:0pt; } 57 | div.td01{ margin-left:0pt; margin-right:5pt; } 58 | div.td10{ margin-left:5pt; margin-right:0pt; } 59 | div.td11{ margin-left:5pt; margin-right:5pt; } 60 | table[rules] {border-left:solid black 0.4pt; border-right:solid black 0.4pt; } 61 | td.td00{ padding-left:0pt; padding-right:0pt; } 62 | td.td01{ padding-left:0pt; padding-right:5pt; } 63 | td.td10{ padding-left:5pt; padding-right:0pt; } 64 | td.td11{ padding-left:5pt; padding-right:5pt; } 65 | table[rules] {border-left:solid black 0.4pt; border-right:solid black 0.4pt; } 66 | .hline hr, .cline hr{ height : 1px; margin:0px; } 67 | .tabbing-right {text-align:right;} 68 | span.TEX {letter-spacing: -0.125em; } 69 | span.TEX span.E{ position:relative;top:0.5ex;left:-0.0417em;} 70 | a span.TEX span.E {text-decoration: none; } 71 | span.LATEX span.A{ position:relative; top:-0.5ex; left:-0.4em; font-size:85%;} 72 | span.LATEX span.TEX{ position:relative; left: -0.4em; } 73 | div.float, div.figure {margin-left: auto; margin-right: auto;} 74 | div.float img {text-align:center;} 75 | div.figure img {text-align:center;} 76 | .marginpar {width:20%; float:right; text-align:left; margin-left:auto; margin-top:0.5em; font-size:85%; text-decoration:underline;} 77 | .marginpar p{margin-top:0.4em; margin-bottom:0.4em;} 78 | table.equation {width:100%;} 79 | .equation td{text-align:center; } 80 | td.equation { margin-top:1em; margin-bottom:1em; } 81 | td.equation-label { width:5%; text-align:center; } 82 | td.eqnarray4 { width:5%; white-space: normal; } 83 | td.eqnarray2 { width:5%; } 84 | table.eqnarray-star, table.eqnarray {width:100%;} 85 | div.eqnarray{text-align:center;} 86 | div.array {text-align:center;} 87 | div.pmatrix {text-align:center;} 88 | table.pmatrix {width:100%;} 89 | span.pmatrix img{vertical-align:middle;} 90 | div.pmatrix {text-align:center;} 91 | table.pmatrix {width:100%;} 92 | span.bar-css {text-decoration:overline;} 93 | img.cdots{vertical-align:middle;} 94 | .partToc a, .partToc, .likepartToc a, .likepartToc {line-height: 200%; font-weight:bold; font-size:110%;} 95 | .index-item, .index-subitem, .index-subsubitem {display:block} 96 | div.caption {text-indent:-2em; margin-left:3em; margin-right:1em; text-align:left;} 97 | div.caption span.id{font-weight: bold; white-space: nowrap; } 98 | h1.partHead{text-align: center} 99 | p.bibitem { text-indent: -2em; margin-left: 2em; margin-top:0.6em; margin-bottom:0.6em; } 100 | p.bibitem-p { text-indent: 0em; margin-left: 2em; margin-top:0.6em; margin-bottom:0.6em; } 101 | .paragraphHead, .likeparagraphHead { margin-top:2em; font-weight: bold;} 102 | .subparagraphHead, .likesubparagraphHead { font-weight: bold;} 103 | .quote {margin-bottom:0.25em; margin-top:0.25em; margin-left:1em; margin-right:1em; text-align:justify;} 104 | .verse{white-space:nowrap; margin-left:2em} 105 | div.maketitle {text-align:center;} 106 | h2.titleHead{text-align:center;} 107 | div.maketitle{ margin-bottom: 2em; } 108 | div.author, div.date {text-align:center;} 109 | div.thanks{text-align:left; margin-left:10%; font-size:85%; font-style:italic; } 110 | .quotation {margin-bottom:0.25em; margin-top:0.25em; margin-left:1em; } 111 | .abstract p {margin-left:5%; margin-right:5%;} 112 | div.abstract {width:100%;} 113 | /* end css.sty */ 114 | 115 | -------------------------------------------------------------------------------- /tests/citation-002/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage[square,sort,comma,authoryear]{natbib} 4 | 5 | \usepackage{url} % format URLs 6 | 7 | \begin{document} 8 | 9 | citet example: \citet{Test:Book,Test:Inproceedings,Test:Article}. 10 | 11 | 12 | ctanchor\,\citep{Test:Book,Test:Inproceedings,Test:Article}. 13 | 14 | 15 | citet example: \citet{Test:Book}. 16 | 17 | 18 | ctanchor\,\citep{Test:Book}. 19 | 20 | 21 | citet example: \citet{Test:Book,Test:Inproceedings}. 22 | 23 | 24 | ctanchor\,\citep{Test:Inproceedings,Test:Article}. 25 | 26 | 27 | \bibliographystyle{abbrvnat} 28 | \bibliography{references} 29 | 30 | \end{document} 31 | -------------------------------------------------------------------------------- /tests/citation-003/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

has been recognized as an issue [Haller, 2015, Swalens 8 | et al., 2014, Tasharofi 9 | et al., 2013], the question of how to

10 |

References

11 |
12 |

   P. Haller. High-Level 13 | Concurrency Libraries: Challenges for Tool Support, 2015.

14 |

   J. Swalens, 15 | S. Marr, J. De Koster, and T. Van Cutsem. 16 | Towards composable concurrency abstractions. In Proc. of the 17 | PLACES Workshop, volume 155, 2014.

18 |

   S. Tasharofi, 19 | P. Dinges, and R. E. Johnson. Why do scala developers mix 20 | the actor model with other concurrency models? In ECOOP 2013 – 21 | Object-Oriented Programming, volume 7920. 2013.

22 |
23 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /tests/citation-003/references.bib: -------------------------------------------------------------------------------- 1 | @incollection{CastagnaECOOP2013, 2 | author = {Tasharofi, Samira and Dinges, Peter and Johnson, Ralph E.}, 3 | booktitle = {ECOOP 2013 – Object-Oriented Programming}, 4 | title = {Why Do Scala Developers Mix the Actor Model with other Concurrency Models?}, 5 | volume = 7920, 6 | year = 2013 7 | } 8 | 9 | @inproceedings{swalens2014towards, 10 | author = {Swalens, Janwillem and Marr, Stefan and De Koster, Joeri and Van Cutsem, Tom}, 11 | booktitle = {Proc. of the PLACES Workshop}, 12 | title = {Towards Composable Concurrency Abstractions}, 13 | volume = 155, 14 | year = 2014 15 | } 16 | 17 | @presentation{Haller:2015:ETX, 18 | author = {Haller, Philipp}, 19 | title = {{High-Level Concurrency Libraries: Challenges for Tool Support}}, 20 | type = {Keynote}, 21 | year = 2015 22 | } 23 | -------------------------------------------------------------------------------- /tests/citation-003/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage[square,sort,comma,authoryear]{natbib} 4 | 5 | \usepackage{url} % format URLs 6 | 7 | \begin{document} 8 | 9 | has been 10 | recognized as an 11 | issue\,\citep{CastagnaECOOP2013,swalens2014towards,Haller:2015:ETX}, the 12 | question of how to 13 | 14 | 15 | \bibliographystyle{abbrvnat} 16 | \bibliography{references} 17 | 18 | \end{document} 19 | -------------------------------------------------------------------------------- /tests/citation-004/ACM-Reference-Format.bst: -------------------------------------------------------------------------------- 1 | ../resources/ACM-Reference-Format.bst -------------------------------------------------------------------------------- /tests/citation-004/acmart.cls: -------------------------------------------------------------------------------- 1 | ../resources/acmart.cls -------------------------------------------------------------------------------- /tests/citation-004/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
1 8 |

Test

9 |

  ACM Reference 10 | Format:

11 |

. 2023. Test. 12 | In Proceedings 13 | of ACM Conference (Conference’17). ACM, 14 | New York, NY, 15 | USA , 16 | 1 page. https://doi.org/\@acmDOI

17 |
18 |

Marr and Ducasse [1] 19 | References

20 |
21 |

[1]    Stefan 22 | Marr and Stéphane 23 | Ducasse. 2015. 24 | Tracing vs. Partial Evaluation: Comparing Meta-Compilation 25 | Approaches for 26 | Self-Optimizing Interpreters. In Proceedings of the 27 | 2015 ACM International 28 | Conference on Object Oriented Programming Systems Languages & 29 | Applications (OOPSLA ’15). ACM, 821–839. https://doi.org/10.1145/2814270.2814275

30 |
31 | ____________________________________________ Permission to make digital or hard 32 | copies of all or part of this work for personal or classroom use is granted 33 | without fee provided that copies are not made or distributed for profit 34 | or commercial advantage and that copies bear this notice and the full 35 | citation on the first page. Copyrights for components of this work owned by 36 | others than ACM must be honored. Abstracting with credit is permitted. 37 | To copy otherwise, or republish, to post on servers or to redistribute to 38 | lists, requires prior specific permission and/or a fee. Request permissions 39 | from permissions@acm.org. Conference’17, July 2017, 40 | Washington, DC, USA © 2023 Association for Computing 41 | Machinery.
42 | ACM ISBN 978-x-xxxx-xxxx-x/YY/MM…$15.00
43 | https://doi.org/\@acmDOI 44 | 45 | 46 |
47 | -------------------------------------------------------------------------------- /tests/citation-004/references.bib: -------------------------------------------------------------------------------- 1 | @inproceedings{Marr:2015:MTPE, 2 | author = {Marr, Stefan and Ducasse, Stéphane}, 3 | booktitle = {Proceedings of the 2015 ACM International Conference on Object Oriented Programming Systems Languages \& Applications}, 4 | doi = {10.1145/2814270.2814275}, 5 | isbn = {978-1-4503-2585-1}, 6 | month = oct, 7 | numpages = {19}, 8 | pages = {821--839}, 9 | publisher = {ACM}, 10 | series = {OOPSLA '15}, 11 | title = {Tracing vs. Partial Evaluation: Comparing Meta-Compilation Approaches for Self-Optimizing Interpreters}, 12 | year = {2015}, 13 | } 14 | -------------------------------------------------------------------------------- /tests/citation-004/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt,sigplan]{acmart} 2 | \begin{document} 3 | 4 | \title{Test} 5 | 6 | \maketitle 7 | 8 | \citet{Marr:2015:MTPE} 9 | 10 | \bibliographystyle{ACM-Reference-Format} 11 | \bibliography{references} 12 | \end{document} 13 | \endinput 14 | -------------------------------------------------------------------------------- /tests/footnote-001/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

main text main text main text main text ftanchor1 1Some footnote text

8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /tests/footnote-001/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \begin{document} 3 | 4 | main text main text main text 5 | main text ftanchor\footnote{Some footnote text} 6 | 7 | \end{document} 8 | -------------------------------------------------------------------------------- /tests/header-abstract-001/expected.html: -------------------------------------------------------------------------------- 1 | 2 | My Title Example 3 | 4 | 5 | 7 |
8 |

My Title Example

Author Number1, Author 9 | Number2
10 |
11 |
12 |
13 |

Abstract

14 |

abstract abstract abstract abstract abstract 15 | abstract abstract abstract abstract 16 | abstract abstract abstract abstract abstract abstract 17 | abstract abstract abstract abstract 18 | abstract abstract abstract abstract abstract

19 |
20 | 21 |
22 |

1   Section 1

23 |

main text main text main text main text main text main text main 24 | text main text

25 |
26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /tests/header-abstract-001/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \begin{document} 4 | 5 | \title{My Title Example} 6 | \author{Author Number1, Author Number2} 7 | \date{} 8 | \maketitle 9 | 10 | \begin{abstract} 11 | 12 | abstract abstract abstract abstract abstract abstract abstract abstract 13 | abstract abstract abstract abstract abstract abstract abstract abstract 14 | abstract abstract abstract abstract abstract abstract abstract abstract 15 | 16 | \end{abstract} 17 | 18 | \section{Section 1} 19 | 20 | main text main text main text main text main text main text main text main text 21 | 22 | \end{document} 23 | -------------------------------------------------------------------------------- /tests/ligatures-001/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

Test ligatures:

8 |

ff fi fl ffi ffl

9 |

Œ œ Æ æ

10 |

IJ ij

11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /tests/ligatures-001/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \begin{document} 3 | 4 | Test ligatures: 5 | 6 | ff fi fl ffi ffl 7 | 8 | \OE{} \oe{} \AE{} \ae{} 9 | 10 | \IJ{} \ij{} 11 | 12 | \end{document} 13 | -------------------------------------------------------------------------------- /tests/listing-001/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

main text main foobar.

8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /tests/listing-001/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{listings} 3 | \begin{document} 4 | main text main \lstinline[basicstyle=\ttfamily]!foobar!. 5 | \end{document} 6 | -------------------------------------------------------------------------------- /tests/listing-002/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

main text before

8 |
9 |
10 |
1 2 3 4
11 |
123456789
12 |   34   89
13 | 12  567
14 | ---------
15 |    
16 |    
17 |
18 |

main text after

19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /tests/listing-002/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{listings} 3 | \begin{document} 4 | 5 | main text before 6 | \begin{lstlisting} 7 | 123456789 8 | 34 89 9 | 12 567 10 | --------- 11 | \end{lstlisting} 12 | main text after 13 | 14 | \end{document} 15 | -------------------------------------------------------------------------------- /tests/resources/ACM-Reference-Format.bst: -------------------------------------------------------------------------------- 1 | %%% -*-BibTeX-*- 2 | %%% ==================================================================== 3 | %%% @BibTeX-style-file{ 4 | %%% author = "Nelson H. F. Beebe, Boris Veytsman and Gerald Murray", 5 | %%% version = "2.1", 6 | %%% acmart-version = "1.79", 7 | %%% date = "14 June 2017", 8 | %%% filename = "ACM-Reference-Format.bst", 9 | %%% email = "borisv@lk.net, boris@varphi.com", 10 | %%% codetable = "ISO/ASCII", 11 | %%% keywords = "ACM Transactions bibliography style; BibTeX", 12 | %%% license = "public domain", 13 | %%% supported = "yes", 14 | %%% abstract = "", 15 | %%% } 16 | %%% ==================================================================== 17 | 18 | %%% Revision history: see source in git 19 | 20 | ENTRY 21 | { address 22 | advisor 23 | archiveprefix 24 | author 25 | booktitle 26 | chapter 27 | city 28 | date 29 | edition 30 | editor 31 | eprint 32 | eprinttype 33 | eprintclass 34 | howpublished 35 | institution 36 | journal 37 | key 38 | location 39 | month 40 | note 41 | number 42 | organization 43 | pages 44 | primaryclass 45 | publisher 46 | school 47 | series 48 | title 49 | type 50 | volume 51 | year 52 | % New keys recognized 53 | issue % UTAH: used in, e.g., ACM SIGSAM Bulletin and ACM Communications in Computer Algebra 54 | articleno 55 | eid 56 | day % UTAH: needed for newspapers, weeklies, bi-weeklies 57 | doi % UTAH 58 | url % UTAH 59 | bookpages % UTAH 60 | numpages 61 | lastaccessed % UTAH: used only for @Misc{...} 62 | coden % UTAH 63 | isbn % UTAH 64 | isbn-13 % UTAH 65 | issn % UTAH 66 | lccn % UTAH 67 | distinctURL % whether to print url if doi is present 68 | } 69 | {} 70 | { label.year extra.label sort.year sort.label basic.label.year} 71 | 72 | INTEGERS { output.state before.all mid.sentence after.sentence after.block } 73 | 74 | INTEGERS { show-isbn-10-and-13 } % initialized below in begin.bib 75 | 76 | INTEGERS { nameptr namesleft numnames } 77 | 78 | INTEGERS { multiresult } 79 | 80 | INTEGERS { len } 81 | 82 | INTEGERS { last.extra.num } 83 | 84 | STRINGS { s t t.org u } 85 | 86 | STRINGS { last.label next.extra } 87 | 88 | STRINGS { p1 p2 p3 page.count } 89 | 90 | 91 | FUNCTION { not } 92 | { 93 | { #0 } 94 | { #1 } 95 | if$ 96 | } 97 | 98 | FUNCTION { and } 99 | { 100 | 'skip$ 101 | { pop$ #0 } 102 | if$ 103 | } 104 | 105 | FUNCTION { or } 106 | { 107 | { pop$ #1 } 108 | 'skip$ 109 | if$ 110 | } 111 | 112 | 113 | FUNCTION { dump.stack.1 } 114 | { 115 | duplicate$ "STACK[top] = [" swap$ * "]" * warning$ 116 | } 117 | 118 | FUNCTION { dump.stack.2 } 119 | { 120 | duplicate$ "STACK[top ] = [" swap$ * "]" * warning$ 121 | swap$ 122 | duplicate$ "STACK[top-1] = [" swap$ * "]" * warning$ 123 | swap$ 124 | } 125 | 126 | FUNCTION { empty.or.unknown } 127 | { 128 | %% Examine the top stack entry, and push 1 if it is empty, or 129 | %% consists only of whitespace, or is a string beginning with two 130 | %% queries (??), and otherwise, push 0. 131 | %% 132 | %% This function provides a replacement for empty$, with the 133 | %% convenient feature that unknown values marked by two leading 134 | %% queries are treated the same as missing values, and thus, do not 135 | %% appear in the output .bbl file, and yet, their presence in .bib 136 | %% file(s) serves to mark values which are temporarily missing, but 137 | %% are expected to be filled in eventually once more data is 138 | %% obtained. The TeX User Group and BibNet bibliography archives 139 | %% make extensive use of this practice. 140 | %% 141 | %% An empty string cannot serve the same purpose, because just as in 142 | %% statistics data processing, an unknown value is not the same as an 143 | %% empty value. 144 | %% 145 | %% At entry: stack = ... top:[string] 146 | %% At exit: stack = ... top:[0 or 1] 147 | 148 | duplicate$ empty$ 149 | { pop$ #1 } 150 | { #1 #2 substring$ "??" = } 151 | if$ 152 | } 153 | 154 | FUNCTION { empty.or.zero } 155 | { 156 | %% Examine the top entry and push 1 if it is empty, or is zero 157 | duplicate$ empty$ 158 | { pop$ #1 } 159 | { "0" = } 160 | if$ 161 | } 162 | 163 | 164 | FUNCTION { writeln } 165 | { 166 | %% In BibTeX style files, the sequences 167 | %% 168 | %% ... "one" "two" output 169 | %% ... "one" "two" output.xxx 170 | %% 171 | %% ship "one" to the output file, possibly following by punctuation, 172 | %% leaving the stack with 173 | %% 174 | %% ... "two" 175 | %% 176 | %% There is thus a one-string lag in output processing that must be 177 | %% carefully handled to avoid duplicating a string in the output 178 | %% file. Unless otherwise noted, all output.xxx functions leave 179 | %% just one new string on the stack, and that model should be born 180 | %% in mind when reading or writing function code. 181 | %% 182 | %% BibTeX's asynchronous buffering of output from strings from the 183 | %% stack is confusing because newline$ bypasses the buffer. It 184 | %% would have been so much easier for newline to be a character 185 | %% rather than a state of the output-in-progress. 186 | %% 187 | %% The documentation in btxhak.dvi is WRONG: it says 188 | %% 189 | %% newline$ Writes onto the bbl file what's accumulated in the 190 | %% output buffer. It writes a blank line if and only 191 | %% if the output buffer is empty. Since write$ does 192 | %% reasonable line breaking, you should use this 193 | %% function only when you want a blank line or an 194 | %% explicit line break. 195 | %% 196 | %% write$ Pops the top (string) literal and writes it on the 197 | %% output buffer (which will result in stuff being 198 | %% written onto the bbl file when the buffer fills 199 | %% up). 200 | %% 201 | %% Examination of the BibTeX source code shows that write$ does 202 | %% indeed behave as claimed, but newline$ sends a newline character 203 | %% directly to the output file, leaving the stack unchanged. The 204 | %% first line "Writes onto ... buffer." is therefore wrong. 205 | %% 206 | %% The original BibTeX style files almost always use "write$ newline$" 207 | %% in that order, so it makes sense to hide that pair in a private 208 | %% function like this one, named after a statement in Pascal, 209 | %% the programming language embedded in the BibTeX Web program. 210 | 211 | write$ % output top-of-stack string 212 | newline$ % immediate write of newline (not via stack) 213 | } 214 | 215 | FUNCTION { init.state.consts } 216 | { 217 | #0 'before.all := 218 | #1 'mid.sentence := 219 | #2 'after.sentence := 220 | #3 'after.block := 221 | } 222 | 223 | FUNCTION { output.nonnull } 224 | { % Stack in: ... R S T Stack out: ... R T File out: S 225 | 's := 226 | output.state mid.sentence = 227 | { 228 | ", " * write$ 229 | } 230 | { 231 | output.state after.block = 232 | { 233 | add.period$ writeln 234 | "\newblock " write$ 235 | } 236 | { 237 | output.state before.all = 238 | { 239 | write$ 240 | } 241 | { 242 | add.period$ " " * write$ 243 | } 244 | if$ 245 | } 246 | if$ 247 | mid.sentence 'output.state := 248 | } 249 | if$ 250 | s 251 | } 252 | 253 | FUNCTION { output.nonnull.dot.space } 254 | { % Stack in: ... R S T Stack out: ... R T File out: S 255 | 's := 256 | output.state mid.sentence = % { ". " * write$ } 257 | { 258 | ". " * write$ 259 | } 260 | { 261 | output.state after.block = 262 | { 263 | add.period$ writeln "\newblock " write$ 264 | } 265 | { 266 | output.state before.all = 267 | { 268 | write$ 269 | } 270 | { 271 | add.period$ " " * write$ 272 | } 273 | if$ 274 | } 275 | if$ 276 | mid.sentence 'output.state := 277 | } 278 | if$ 279 | s 280 | } 281 | 282 | FUNCTION { output.nonnull.remove } 283 | { % Stack in: ... R S T Stack out: ... R T File out: S 284 | 's := 285 | output.state mid.sentence = 286 | { 287 | " " * write$ 288 | } 289 | { 290 | output.state after.block = 291 | { 292 | add.period$ writeln "\newblock " write$ 293 | } 294 | { 295 | output.state before.all = 296 | { 297 | write$ 298 | } 299 | { 300 | add.period$ " " * write$ 301 | } 302 | if$ 303 | } 304 | if$ 305 | mid.sentence 'output.state := 306 | } 307 | if$ 308 | s 309 | } 310 | 311 | FUNCTION { output.nonnull.removenospace } 312 | { % Stack in: ... R S T Stack out: ... R T File out: S 313 | 's := 314 | output.state mid.sentence = 315 | { 316 | "" * write$ 317 | } 318 | { 319 | output.state after.block = 320 | { 321 | add.period$ writeln "\newblock " write$ 322 | } 323 | { 324 | output.state before.all = 325 | { 326 | write$ 327 | } 328 | { 329 | add.period$ " " * write$ 330 | } 331 | if$ 332 | } 333 | if$ 334 | mid.sentence 'output.state := 335 | } 336 | if$ 337 | s 338 | } 339 | 340 | FUNCTION { output } 341 | { % discard top token if empty, else like output.nonnull 342 | duplicate$ empty.or.unknown 343 | 'pop$ 344 | 'output.nonnull 345 | if$ 346 | } 347 | 348 | FUNCTION { output.dot.space } 349 | { % discard top token if empty, else like output.nonnull.dot.space 350 | duplicate$ empty.or.unknown 351 | 'pop$ 352 | 'output.nonnull.dot.space 353 | if$ 354 | } 355 | 356 | FUNCTION { output.removenospace } 357 | { % discard top token if empty, else like output.nonnull.removenospace 358 | duplicate$ empty.or.unknown 359 | 'pop$ 360 | 'output.nonnull.removenospace 361 | if$ 362 | } 363 | 364 | FUNCTION { output.check } 365 | { % like output, but warn if key name on top-of-stack is not set 366 | 't := 367 | duplicate$ empty.or.unknown 368 | { pop$ "empty " t * " in " * cite$ * warning$ } 369 | 'output.nonnull 370 | if$ 371 | } 372 | 373 | FUNCTION { bibinfo.output.check } 374 | { % like output.check, adding bibinfo field 375 | 't := 376 | duplicate$ empty.or.unknown 377 | { pop$ "empty " t * " in " * cite$ * warning$ } 378 | { "\bibinfo{" t "}{" * * swap$ * "}" * 379 | output.nonnull } 380 | if$ 381 | } 382 | 383 | FUNCTION { output.check.dot.space } 384 | { % like output.dot.space, but warn if key name on top-of-stack is not set 385 | 't := 386 | duplicate$ empty.or.unknown 387 | { pop$ "empty " t * " in " * cite$ * warning$ } 388 | 'output.nonnull.dot.space 389 | if$ 390 | } 391 | 392 | FUNCTION { fin.block } 393 | { % functionally, but not logically, identical to fin.entry 394 | add.period$ 395 | writeln 396 | } 397 | 398 | FUNCTION { fin.entry } 399 | { 400 | add.period$ 401 | writeln 402 | } 403 | 404 | FUNCTION { new.sentence } 405 | { % update sentence state, with neither output nor stack change 406 | output.state after.block = 407 | 'skip$ 408 | { 409 | output.state before.all = 410 | 'skip$ 411 | { after.sentence 'output.state := } 412 | if$ 413 | } 414 | if$ 415 | } 416 | 417 | FUNCTION { fin.sentence } 418 | { 419 | add.period$ 420 | write$ 421 | new.sentence 422 | "" 423 | } 424 | 425 | FUNCTION { new.block } 426 | { 427 | output.state before.all = 428 | 'skip$ 429 | { after.block 'output.state := } 430 | if$ 431 | } 432 | 433 | FUNCTION { output.coden } % UTAH 434 | { % output non-empty CODEN as one-line sentence (stack untouched) 435 | coden empty.or.unknown 436 | { } 437 | { "\showCODEN{" coden * "}" * writeln } 438 | if$ 439 | } 440 | 441 | % 442 | % Sometimes articleno starts with the word 'Article' or 'Paper. 443 | % (this is a bug of acmdl, sigh) 444 | % We strip them. We assume eid or articleno is already on stack 445 | % 446 | 447 | FUNCTION { strip.articleno.or.eid } 448 | { 449 | 't := 450 | t #1 #7 substring$ "Article" = 451 | {t #8 t text.length$ substring$ 't :=} 452 | { } 453 | if$ 454 | t #1 #7 substring$ "article" = 455 | {t #8 t text.length$ substring$ 't :=} 456 | { } 457 | if$ 458 | t #1 #5 substring$ "Paper" = 459 | {t #6 t text.length$ substring$ 't :=} 460 | { } 461 | if$ 462 | t #1 #5 substring$ "paper" = 463 | {t #6 t text.length$ substring$ 't :=} 464 | { } 465 | if$ 466 | % Strip any left trailing space or ~ 467 | t #1 #1 substring$ " " = 468 | {t #2 t text.length$ substring$ 't :=} 469 | { } 470 | if$ 471 | t #1 #1 substring$ "~" = 472 | {t #2 t text.length$ substring$ 't :=} 473 | { } 474 | if$ 475 | t 476 | } 477 | 478 | 479 | FUNCTION { format.articleno } 480 | { 481 | articleno empty.or.unknown not eid empty.or.unknown not and 482 | { "Both articleno and eid are defined for " cite$ * warning$ } 483 | 'skip$ 484 | if$ 485 | articleno empty.or.unknown eid empty.or.unknown and 486 | { "" } 487 | { 488 | numpages empty.or.unknown 489 | { "articleno or eid field, but no numpages field, in " 490 | cite$ * warning$ } 491 | { } 492 | if$ 493 | eid empty.or.unknown 494 | { "Article \bibinfo{articleno}{" articleno strip.articleno.or.eid * "}" * } 495 | { "Article \bibinfo{articleno}{" eid strip.articleno.or.eid * "}" * } 496 | if$ 497 | } 498 | if$ 499 | } 500 | 501 | FUNCTION { format.year } 502 | { % push year string or "[n.\,d.]" onto output stack 503 | %% Because year is a mandatory field, we always force SOMETHING 504 | %% to be output 505 | "\bibinfo{year}{" 506 | year empty.or.unknown 507 | { "[n.\,d.]" } 508 | { year } 509 | if$ 510 | * "}" * 511 | } 512 | 513 | FUNCTION { format.day.month } 514 | { % push "day month " or "month " or "" onto output stack 515 | day empty.or.unknown 516 | { 517 | month empty.or.unknown 518 | { "" } 519 | { "\bibinfo{date}{" month * "} " *} 520 | if$ 521 | } 522 | { 523 | month empty.or.unknown 524 | { "" } 525 | { "\bibinfo{date}{" day * " " * month * "} " *} 526 | if$ 527 | } 528 | if$ 529 | } 530 | 531 | FUNCTION { format.day.month.year } % UTAH 532 | { % if month is empty, push "" else push "(MON.)" or "(DD MON.)" 533 | % Needed for frequent periodicals: 2008. ... New York Times C-1, C-2, C-17 (23 Oct.) 534 | % acm-*.bst addition: prefix parenthesized date string with 535 | % ", Article nnn " 536 | articleno empty.or.unknown eid empty.or.unknown and 537 | { "" } 538 | { output.state after.block = 539 | {", " format.articleno * } 540 | { format.articleno } 541 | if$ 542 | } 543 | if$ 544 | " (" * format.day.month * format.year * ")" * 545 | } 546 | 547 | FUNCTION { output.day.month.year } % UTAH 548 | { % if month is empty value, do nothing; else output stack top and 549 | % leave with new top string "(MON.)" or "(DD MON.)" 550 | % Needed for frequent periodicals: 2008. ... New York Times C-1, C-2, C-17 (23 Oct.) 551 | format.day.month.year 552 | output.nonnull.remove 553 | } 554 | 555 | FUNCTION { strip.doi } % UTAH 556 | { % Strip any Web address prefix to recover the bare DOI, leaving the 557 | % result on the output stack, as recommended by CrossRef DOI 558 | % documentation. 559 | % For example, reduce "http://doi.acm.org/10.1145/1534530.1534545" to 560 | % "10.1145/1534530.1534545". A suitable URL is later typeset and 561 | % displayed as the LAST item in the reference list entry. Publisher Web 562 | % sites wrap this with a suitable link to a real URL to resolve the DOI, 563 | % and the master https://doi.org/ address is preferred, since publisher- 564 | % specific URLs can disappear in response to economic events. All 565 | % journals are encouraged by the DOI authorities to use that typeset 566 | % format and link procedures for uniformity across all publications that 567 | % include DOIs in reference lists. 568 | % The numeric prefix is guaranteed to start with "10.", so we use 569 | % that as a test. 570 | % 2017-02-04 Added stripping of https:// (Boris) 571 | doi #1 #3 substring$ "10." = 572 | { doi } 573 | { 574 | doi 't := % get modifiable copy of DOI 575 | 576 | % Change https:// to http:// to strip both prefixes (BV) 577 | 578 | t #1 #8 substring$ "https://" = 579 | { "http://" t #9 t text.length$ #8 - substring$ * 't := } 580 | { } 581 | if$ 582 | 583 | t #1 #7 substring$ "http://" = 584 | { 585 | t #8 t text.length$ #7 - substring$ 't := 586 | 587 | "INTERNAL STYLE-FILE ERROR" 's := 588 | 589 | % search for next "/" and assign its suffix to s 590 | 591 | { t text.length$ } 592 | { 593 | t #1 #1 substring$ "/" = 594 | { 595 | % save rest of string as true DOI (should be 10.xxxx/yyyy) 596 | t #2 t text.length$ #1 - substring$ 's := 597 | "" 't := % empty string t terminates the loop 598 | } 599 | { 600 | % discard first character and continue loop: t <= substring(t,2,last) 601 | t #2 t text.length$ #1 - substring$ 't := 602 | } 603 | if$ 604 | } 605 | while$ 606 | 607 | % check for valid DOI (should be 10.xxxx/yyyy) 608 | s #1 #3 substring$ "10." = 609 | { } 610 | { "unrecognized DOI substring " s * " in DOI value [" * doi * "]" * warning$ } 611 | if$ 612 | 613 | s % push the stripped DOI on the output stack 614 | 615 | } 616 | { 617 | "unrecognized DOI value [" doi * "]" * warning$ 618 | doi % push the unrecognized original DOI on the output stack 619 | } 620 | if$ 621 | } 622 | if$ 623 | } 624 | 625 | % 626 | % Change by BV: added standard prefix to URL 627 | % 628 | FUNCTION { output.doi } % UTAH 629 | { % output non-empty DOI as one-line sentence (stack untouched) 630 | doi empty.or.unknown 631 | { } 632 | { 633 | %% Use \urldef here for the same reason it is used in output.url, 634 | %% see output.url for further discussion. 635 | "\urldef\tempurl%" writeln 636 | "\url{https://doi.org/" strip.doi * "}" * writeln 637 | "\showDOI{\tempurl}" writeln 638 | } 639 | if$ 640 | } 641 | 642 | FUNCTION { output.isbn } % UTAH 643 | { % output non-empty ISBN-10 and/or ISBN-13 as one-line sentences (stack untouched) 644 | show-isbn-10-and-13 645 | { 646 | %% show both 10- and 13-digit ISBNs 647 | isbn empty.or.unknown 648 | { } 649 | { 650 | "\showISBNx{" isbn * "}" * writeln 651 | } 652 | if$ 653 | isbn-13 empty.or.unknown 654 | { } 655 | { 656 | "\showISBNxiii{" isbn-13 * "}" * writeln 657 | } 658 | if$ 659 | } 660 | { 661 | %% show 10-digit ISBNs only if 13-digit ISBNs not available 662 | isbn-13 empty.or.unknown 663 | { 664 | isbn empty.or.unknown 665 | { } 666 | { 667 | "\showISBNx{" isbn * "}" * writeln 668 | } 669 | if$ 670 | } 671 | { 672 | "\showISBNxiii{" isbn-13 * "}" * writeln 673 | } 674 | if$ 675 | } 676 | if$ 677 | } 678 | 679 | FUNCTION { output.issn } % UTAH 680 | { % output non-empty ISSN as one-line sentence (stack untouched) 681 | issn empty.or.unknown 682 | { } 683 | { "\showISSN{" issn * "}" * writeln } 684 | if$ 685 | } 686 | 687 | FUNCTION { output.issue } 688 | { % output non-empty issue number as a one-line sentence (stack untouched) 689 | issue empty.or.unknown 690 | { } 691 | { "Issue " issue * "." * writeln } 692 | if$ 693 | } 694 | 695 | FUNCTION { output.lccn } % UTAH 696 | { % return with stack untouched 697 | lccn empty.or.unknown 698 | { } 699 | { "\showLCCN{" lccn * "}" * writeln } 700 | if$ 701 | } 702 | 703 | FUNCTION { output.note } % UTAH 704 | { % return with stack empty 705 | note empty.or.unknown 706 | { } 707 | { "\shownote{" note * "}" add.period$ * writeln } 708 | if$ 709 | } 710 | 711 | FUNCTION { output.note.check } % UTAH 712 | { % return with stack empty 713 | note empty.or.unknown 714 | { "empty note in " cite$ * warning$ } 715 | { "\shownote{" note * "}" add.period$ * writeln } 716 | if$ 717 | } 718 | 719 | FUNCTION { output.eprint } % 720 | { % return with stack empty 721 | eprint empty.or.unknown 722 | { } 723 | { "\showeprint" 724 | archiveprefix empty.or.unknown 725 | { eprinttype empty.or.unknown 726 | { } 727 | { "[" eprinttype "]" * * * } 728 | if$ 729 | } 730 | { "[" archiveprefix "l" change.case$ "]" * * * } 731 | if$ 732 | "{" eprint "}" * * * 733 | primaryclass empty.or.unknown 734 | { eprintclass empty.or.unknown 735 | { } 736 | { "~[" eprintclass "]" * * * } 737 | if$ 738 | } 739 | { "~[" primaryclass "]" * * * } 740 | if$ 741 | writeln 742 | } 743 | if$ 744 | } 745 | 746 | 747 | % 748 | % Changes by BV 2011/04/15. Do not output 749 | % url if doi is defined 750 | % 751 | % 752 | % Changes by BV 2021/11/26. Output url even if doi is defined 753 | % if distinctURL is not zero. 754 | % 755 | FUNCTION { output.url } % UTAH 756 | { % return with stack untouched 757 | % output URL and associated lastaccessed fields 758 | doi empty.or.unknown distinctURL empty.or.zero not or 759 | { 760 | url empty.or.unknown 761 | { } 762 | { 763 | %% Use \urldef, outside \showURL, so that %nn, #, etc in URLs work 764 | %% correctly. Put the actual URL on its own line to reduce the 765 | %% likelihood of BibTeX's nasty line wrapping after column 79. 766 | %% \url{} can undo this, but if that doesn't work for some reason 767 | %% the .bbl file would have to be repaired manually. 768 | "\urldef\tempurl%" writeln 769 | "\url{" url * "}" * writeln 770 | 771 | "\showURL{%" writeln 772 | lastaccessed empty.or.unknown 773 | { "" } 774 | { "Retrieved " lastaccessed * " from " * } 775 | if$ 776 | "\tempurl}" * writeln 777 | } 778 | if$ 779 | } 780 | { } 781 | if$ 782 | } 783 | 784 | FUNCTION { output.year.check } 785 | { % warn if year empty, output top string and leave " YEAR