Accessing YAML parameters as macros within externa

2019-04-26 18:36发布

问题:

I'm looking for ways to add variables (or LaTeX macros) to the YAML header or soon after such that they can be used in external .tex files that are a part of my (modularised) report.

My .rmd file

---
output:
  pdf_document:
    latex_engine: xelatex
    includes:
      before_body: some.tex
params:
  cat: "Felix"
  numb: 14
---

# chapter
Oh my \textbf{`r params$cat`}. 
$x = `r 2*params$numb`^2$

<!-- Trying again to get the parameter -->
\input{some.tex}

My some.tex file:

`r params\$cat`

Output

Hoped-for output

I want to be able to somehow pass the variables from the YAML header (or even just below it) to be used by LaTeX so that all important and regularly updated parameters can be viewed and changed in one place.

回答1:

If you are looking for something which perhaps is most in keeping with the R Markdown workflow, you can customise the template which is used to build the LaTeX output and add all the extra LaTeX code directly to this.

1. Copying Template

Firstly, we must make a copy of the template used by R Markdown. The following code will create this in your current working directory:

file.copy(system.file("rmd/latex/default-1.17.0.2.tex",
          package = "rmarkdown"), "template.tex")

2. Adding Variables

With our copy, we can define our own pandoc variables which will be inserted into the output document. This allows us to specify parameters in the YAML section of the document and they will be updated in the output format. It is exactly the same mechanism which allows us to add title, author, date and for them to be added to the output format.

I have added some code to the front matter of the document at lines 253-255. The exact location doesn't matter, but I also tend to put my customisations before the \begin{document} argument:

\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhead[LO, LE]{$params.value$}
\fancyhead[RO, RE]{$yourParam$}

3. Calling Template from R Markdown

We can reference the custom template to our R Markdown document as explained here. Here is my minimal example:

---
output:
  pdf_document:
    template: template.tex
params:
  value: Text
yourParam: "`r Sys.Date()`"
---

`r params$value`

The two parameters will be added to the output replacing the $params.value$ and $yourParam$, and result in the output below:

The example highlights how the YAML parameters don't have to be nested within the params argument, as specified in your original question. Having them specified within the parameters mainly has benefits if you want to build a parameterized report

Note: the approach of replacing variables using the pandoc notation $variable$ is only possible for the main template file defined under the template option. It won't work for any of the includes arguments or any other external LaTeX files. See here for more details.



回答2:

This comes only half-way. Still no file as header-input...

Maybe this answer will give someone else an idea to build on..

---
output:
  pdf_document:
    latex_engine: xelatex
params:
  cat: "Felix"
  numb: 14
header-includes:
- \usepackage{fancyhdr}
- \pagestyle{fancy}
- \fancyhead[CO,CE]{`r params$cat`}
---

# CHAPTER 1
Oh my \textbf{`r params$cat`}. 
$x = `r 2*params$numb`^2$

```{r child = 'some.tex'}
```

screenshot pdf



回答3:

I'm sure you can code something together using lua-filters.

First come up with your own include-mechanism (since this needs to happen before variable-substitution, so you cannot use latex's \input), but e.g. this filter:

function Para (elem)
  if #elem.content == 1 and elem.content[1].t == "Image" then
    local img = elem.content[1]
    if img.classes[1] == "markdown" then
      local f = io.open(img.src, 'r')
      local blocks = pandoc.read(f:read('*a')).blocks
      f:close()
      return blocks
    end
  end
end

Then do the variable substitution with e.g. this filter:

local vars = {}

function get_vars (meta)
  for k, v in pairs(meta) do
    if v.t == 'MetaInlines' then
      vars["$" .. k .. "$"] = {table.unpack(v)}
    end
  end
end

function replace (el)
  if vars[el.text] then
    return pandoc.Span(vars[el.text])
  else
    return el
  end
end

return {{Meta = get_vars}, {Str = replace}}

This should then work like:

---
output:
  pdf_document:
    latex_engine: xelatex
    pandoc_args:
      - '--lua-filter=include.lua'
      - '--lua-filter=substitution.lua'
name: Samuel
---

Look, I can include files:

![](include.md){.markdown}

And in include.md:

Look, I can use variables: \$name\$


回答4:

You can create a TeX or LaTeX macro in the YAML header, and use that in your some.tex file.

For example, put this in your main file:

---
output:
  pdf_document:
    latex_engine: xelatex
      includes:
        before_body: some.tex
header-includes:
- \def\thecat{`r params$cat`}
params:
  cat: "Felix"
  numb: 14
---


# chapter
Oh my \textbf{`r params$cat`}. 
$x = `r 2*params$numb`^2$

\input{some.tex}

and put this in some.tex:

\thecat

and things will display the way you want.



回答5:

Let me rephrase the question: The goal is to write a parametrized report. This report uses \input to embed a TEX file (e.g. some.tex). You are looking for a way to access the YAML parameters in some.tex.

One way to do this is to define LaTeX macros according to the YAML parameters, e.g. the YAML parameter cat: "Felix" becomes \newcommand{\cat}{Felix}. These TEX macros can then be used throughout the document. (In the main file, r params$cat would work as well, but as some.tex is not being knitted, here only \cat works.)

First, the R code is evaluated. It reads all YAML parameters and writes corresponding macros into myparams.tex.1 Via header-includes the generated file myparams.tex is included in the intermediate TEX file, which is finally being compiled to PDF.

Main RMD file:

---
output: pdf_document
header-includes: "\\input{myparams.tex}"
params:
  cat: "Felix"
  numb: 14
---

```{r, include = FALSE}
if (file.exists("myparams.tex")) {
  file.remove("myparams.tex") # CAUTION: this will DELETE any existing file "myparams.tex"
}
for (param in names(params)) {
  cat(sprintf("\\newcommand{\\%s}{%s}\n", param, params[param]), file = "myparams.tex", append = TRUE)
}
```

Oh my \cat. 
$x = 2 \cdot \numb^2$

Or alternatively: Oh my `r params$cat`. 

\input{some.tex}

some.tex:

Oh my \cat. % Here, `r params$cat` won't work.

Output:

Oh my Felix. x = 2 · 14²

Or alternatively: Oh my Felix.

Oh my Felix, again.

Contents of myparams.tex (dynamically generated):

\newcommand{\cat}{Felix}
\newcommand{\numb}{14}

1 It might be a good idea to use a unique prefix for all new LaTeX macros to avoid clashes between build-in LaTeX commands and YAML parameters with the same name.