Skip to contents

Put a form's layer into a .qgs with surgical xml2 edits, leaving the project at whatever version QGIS stamped it. Until this existed, rfp_form_create() copied the GeoPackage and its style into a project and never touched the .qgs - so the form was on disk but invisible, and had to be added in QGIS Desktop before anyone could collect with it. That desktop step was the last thing standing between a project and being assembled end to end headlessly.

Usage

rfp_qgs_form_add(
  qgs,
  type,
  name = NULL,
  qml = NULL,
  group = "Forms",
  position = c("top", "bottom"),
  visible = TRUE,
  themes = "all",
  restyle = FALSE,
  backup = TRUE
)

Arguments

qgs

Character. Path to the .qgs.

type

Character. Form type - see rfp_form_types().

name

Character. Layer name as it appears in QGIS. Defaults to " Form <label>" from the registry. The leading space is deliberate: it sorts the blank forms to the top of every layer list and dropdown, which is how every field project already names them.

qml

Character. A style to use instead of the one the form ships, for a form restyled in QGIS Desktop. NULL (default) uses the shipped sidecar if there is one.

group

Character. Layer-tree group, matched on trimmed name and created when absent. Defaults to "Forms", which both shipped templates carry as the first group in the tree.

position

One of "top" (default) or "bottom" - where in the group.

visible

Logical. Checked in the layer tree. Default TRUE.

themes

Character. Map themes to add the layer to. A layer absent from a theme is hidden whenever that theme is applied, so "all" (the default) keeps it visible; character(0) adds it to none.

restyle

Logical. Refresh the style of a form this project already holds, instead of returning no-op. Default FALSE. See below.

backup

Logical. Write <qgs>.bak before a restyle = TRUE edit. Default TRUE. Ignored on the insert path, which creates a layer rather than replacing one and so has nothing to lose.

Value

Invisibly, the layer id.

Details

rfp_qgs_form_add(qgs, "observation")

The form's GeoPackage and .qml are created in the project directory when they are not already there, so one call is enough for a project that has never held this form.

Why a form layer is harder than a raster

A <maplayer> is a source block followed by a style block, and the style block is a .qml - verified element-for-element (#17). So the insert builds the source block and copies the QML's children straight in, the same move rfp_qgs_raster_add() makes. What differs is everything the style carries: tab layout, field aliases, value maps, photo widgets and default expressions, none of which anything here generates. A form with no shipped style is therefore added bare, with a warning - the layer collects data, but its columns get QGIS's generated layout rather than the form.

Adding the same form twice does nothing

The check is on the datasource, not on the layer id. Both shipped templates already declare Form PSCIS under an id QGIS minted, which .qgs_layer_id() - deriving its id by digest so a re-run is idempotent - will never reproduce. An id check would therefore add a second Form PSCIS over the same GeoPackage every time it ran.

Refreshing a form the project already holds

A form's style is embedded in the .qgs - the insert copies the QML's children straight into the <maplayer>. rfp_form_build() writes only the GeoPackage and its sidecar, and QGIS does not re-read a sidecar for a layer a project already declares. So after a schema change the project holds a table with the new columns under a <fieldConfiguration> / <attributeEditorForm> / <defaults> block that predates them: the new fields are unreachable, because a tab layout renders only what attributeEditorForm names, and NULL, because the now() defaults live in <defaults>.

That failure is silent in the worst way - every structural check passes, and so does QGIS Desktop, where the form opens and looks correct. It is only wrong on the device.

restyle = TRUE finds the <maplayer> by datasource and table, reuses the <id> and <layername> it already carries, rebuilds the node from the current style and replaces it. Layer tree, layerorder, map themes, custom-order, the legacy legend and any <relation> referencing the layer are preserved by construction, because none of them is touched. Removing and re-adding would not: .prune_empty_groups() drops a group once its last layer goes and the theme entries go with it, and the id changes.

The id is read from the node, never derived. A raster's id is a digest of name and relative path, so rfp_qgs_raster_add() can reproduce it; a form's is as often minted by QGIS, which is the same reason the no-op check above is on the datasource rather than the id.

Child tables are restyled with the parent - cabin_visit owns cabin_visit_pebble in the same GeoPackage, and its node embeds a style too.

Two guards, both failing toward refusal because this replaces a node rather than adding one. An ambiguous match stops: two styled views over one GeoPackage differ only by name (#202), and restyling both from one QML would be wrong. A missing style stops rather than replacing a styled node with a bare one - the add path warns and continues, which is right for a new layer and wrong here.

What a restyle does not carry over is anything QGIS wrote that rfp does not generate: provider options appended to the datasource, and <extent> / <wgs84extent> on a form that has collected records. QGIS recomputes both.

Examples

qgs <- system.file("testdata", "raster_add_fixture.qgs", package = "rfp")
dir <- withr::local_tempdir()
file.copy(qgs, fs::path(dir, "p.qgs"))
#> Warning: cannot create file '/tmp/RtmpfSNDuG/file1c0867341596/p.qgs', reason 'No such file or directory'
#> [1] FALSE

rfp_qgs_form_add(fs::path(dir, "p.qgs"), "observation")
#> Error: File does not exist: /tmp/RtmpfSNDuG/file1c0867341596/p.qgs

# the GeoPackage and its style were created beside the project
fs::path_file(fs::dir_ls(dir))
#> Error: [ENOENT] Failed to search directory '/tmp/RtmpfSNDuG/file1c0867341596': no such file or directory