Skip to contents

The return leg of rfp_qgs_style_export(). That function lifts a layer's style out to a .qml and never writes the project; this one puts an edited .qml back into a layer the project already declares.

Usage

rfp_qgs_style_set(qgs, layer, qml, elements = NULL, backup = TRUE)

Arguments

qgs

Character. Path to the project .qgs.

layer

Character. The layer's display name, as it appears in the layer tree. An unknown name, or one shared by more than one layer, is an error naming the candidates - never a guess.

qml

Character. Path to the .qml to apply.

elements

Character vector of style element names to move, or NULL (default) for the policy above. A name the .qml does not carry is an error listing what it does - a typo must not silently do nothing.

backup

Logical. Write <qgs>.bak before mutating. Default TRUE. A call that changes nothing writes no backup, because it writes nothing at all.

Value

Invisibly, a data frame with one row per moved element: tag and action ("replaced", or "added:" plus where it was anchored). Zero rows when the style was already what the layer carried.

Details

The style block is replaced in place, so the layer keeps its identity. The <id> and <layername> are read from the node being edited and never derived, and the node is never rebuilt - so every surface holding that id still points at a node that exists: the layer tree, layerorder, custom-order, map themes, the legacy legend, any <relation>. The <datasource> is untouched too, which is how a provider filter (|subset=) survives: not because it is carried, but because nothing rewrites it.

That matters because the alternative loses curated state. Removing and re-adding a layer takes its theme membership with it - measured on one layer, theme entries went 1 to 0 to 5, because membership is enumerated per theme and a re-add joins all of them.

Which elements move

A .qml carries around 35 elements and they are not all symbology. By default this moves the ones that describe how the layer draws - the renderer, labelling, diagrams, blend modes, opacity, the legend, custom properties, the map tip - and holds back the ones that describe the layer's own schema: fieldConfiguration, aliases, defaults, attributeEditorForm, constraints, the editform* family and previewExpression. A style authored against one layer names that layer's columns, and QGIS answers a widget or a label pointing at a column that is not there by silently drawing nothing.

Pass elements = to override, naming exactly the tags to move.

Not every element can be checked, and the ones that cannot are named rather than implied. Eight surfaces carry a column-reference rule: the renderer's attr and rule filters, labelling's field names, the diagram field, temporal's start and end fields while enabled, the map tip, the attribute-table preview inside customproperties, and fieldConfiguration, aliases, defaults, attributeEditorForm and previewExpression. Everything else is applied without one, for one of three reasons:

  • it references no column at all - flags, blendMode, legend, widgets and the rest of the appearance block;

  • it references columns only while disabled - temporal, whose startField and endField are checked when the block says enabled="1" and ignored when it does not, because a disabled temporal filter reads no column;

  • it references columns, and a stale entry is inert - constraints, constraintExpressions, splitPolicies, attributetableconfig, editable, labelOnTop, reuseLastValue. There is nothing to constrain, size, enable or label on a column that is not there, and the shipped templates prove it: Form PSCIS carries 78 flag entries naming columns its own fieldConfiguration lacks, and that form works. Checking these would refuse rfp's own artifacts.

An element rfp does not recognise is in neither group, because rfp cannot know which.

Naming any unchecked element in elements = warns, saying it was applied without a column check. The default policy applies them silently - that is the policy - but an explicit opt-in is someone asking for this one, and silence there is what makes a promise of "checked the same as any other" false.

What is refused

A moved element naming a column the layer does not have is an error, and the file is not touched. That is stricter than the same check while adding a layer, which warns and drops - adding is a bulk operation that must not abort, while restyling one named layer is someone asking for this one. A categorized renderer whose attr names a missing column paints every feature in the fallback class; a label on a missing column draws nothing; neither reports anything.

A layer whose columns rfp cannot read - a WMS or ArcGIS service, a Postgres table, another OGR driver, a file that is absent or locked - is written unchecked, with a message saying so. Absence has to be proven (see ?rfp-layout); refusing on a table rfp merely could not read would make this unusable on exactly the layers it cannot judge.

Examples

project <- file.path(tempdir(), "style_set_example")
dir.create(project)
file.copy(
  list.files(
    system.file("testdata", "subset_fixture", "parent", package = "rfp"),
    full.names = TRUE),
  project
)
#> [1] TRUE TRUE TRUE
qgs <- file.path(project, "parent.qgs")
qml <- system.file("testdata", "nodes", "vector_ogr_line.qml", package = "rfp")

# The renderer, labelling and appearance move; the schema surfaces are held.
rfp_qgs_style_set(qgs, "streams", qml, backup = FALSE)
#>  Layer attributes set from the style root: autoRefreshMode, autoRefreshTime, hasScaleBasedVisibilityFlag, labelsEnabled, layerType, maxScale, minScale, readOnly, simplifyAlgorithm, simplifyDrawingHints, simplifyDrawingTol, simplifyLocal, simplifyMaxScale, and symbologyReferenceScale
#>  "streams": 0 elements replaced, 21 added, 14 held

# Re-applying the same style writes nothing at all.
rfp_qgs_style_set(qgs, "streams", qml, backup = FALSE)
#>  "streams" already carries that style - nothing to do

# A style authored against a different layer is refused, naming the columns it
# wants and the ones the layer has. Nothing is written.
try(rfp_qgs_style_set(
  qgs, "streams",
  system.file("extdata", "forms", "form_pscis.qml", package = "rfp"),
  backup = FALSE))
#> Error : This style names column(s) 'streams' does not have:
#>   <labeling> pscis_crossing_id, my_crossing_reference, overall_rank
#> Available: fid, geom, id, name

unlink(project, recursive = TRUE)