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>.bakbefore arestyle = TRUEedit. DefaultTRUE. Ignored on the insert path, which creates a layer rather than replacing one and so has nothing to lose.
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.
See also
Other qgs:
rfp_qgs_layer_rm(),
rfp_qgs_preview_set(),
rfp_qgs_raster_add(),
rfp_qgs_service_add(),
rfp_qgs_service_rm(),
rfp_qgs_services(),
rfp_qgs_style_export(),
rfp_qgs_style_set(),
rfp_qgs_subset_get(),
rfp_qgs_subset_set(),
rfp_qgs_theme_add(),
rfp_qgs_theme_lookup(),
rfp_qgs_theme_names(),
rfp_qgs_theme_rm(),
rfp_qgs_theme_set(),
rfp_qgs_theme_unbury(),
rfp_qgs_themes(),
rfp_qgs_vector_add(),
rfp_qwc_config_create(),
rfp_raster_style_path(),
rfp_raster_styles()
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
