Files
qelectrotech-source-mirror/sources/autoNum/assignvariables.cpp
T
ispyisail 7c0e867226 Rewrite element-prefix lookup for nesting and multi-tree common collections
The remaining two defects from bugtracker #671's original analysis,
which #686 knowingly didn't cover (see that PR's review thread and the
comment on the now-closed #672).

## #671 item 5: the XML matching ignored nesting

prefixFromLabelFile() was a flat token scan: it matched any <category
name="..."> whose name equalled the next path segment, with no check
that the match was actually a *child* of the previous match. It gave
correct results on the shipped 10_electric/qet_labels.xml only because
that file's document order happens to line up with its hierarchy --
any file with a same-named category at the wrong nesting depth would
silently return the wrong prefix.

Reproduced with a synthetic file where a top-level sibling category
happens to share a name with what should be an unmatched grandchild:
the old (already re-verified-fixed-for-whitespace) lookup returns a
prefix from a completely unrelated branch of the document; this
rewrite correctly reports "not found".

Fixed by replacing the QXmlStreamReader token walk with a QDomDocument
walk that only ever considers a matched node's direct <category>
children (firstChildElement()/nextSiblingElement(), scoped to that
node), which cannot cross into a same-named sibling subtree. This also
makes the whitespace-dependence fixed in #686 moot for the same
reason: DOM parsing doesn't distinguish pretty-printed from minified
input to begin with.

The inheritance rule ("if a directory has no prefix, use its parent's,
and so on") and the empty-<prefix/>-overrides-inheritance behaviour
#686 added both carry over unchanged: a category's own <prefix> child,
even an empty one, always overrides whatever a shallower ancestor
already provided; a category with no <prefix> child at all leaves the
inherited value untouched.

## #671 item 2: common-collection trees other than 10_electric

The lookup only ever consulted commonElementsDir()/10_electric --
literally: `if (current_location.fileName() == "10_electric")`. The
common collection ships four other top-level trees (20_logic,
30_hydraulic, 50_pneumatic, 60_energy); none of them could carry a
qet_labels.xml at all, because nothing ever looked for one.

Generalised to commonElementsDir()/<tree>/qet_labels.xml for whichever
top-level tree the element's path actually walks up to, tried first,
then custom, then company -- each of the latter two tried against both
a from-root layout (matching a custom/company file organised as a
mirror of the common collection, tree name included) and a
tree-relative one (matching a file scoped to just one tree), so
existing custom files keep working either way. This is the same
multi-candidate structure #686 already established for custom-then-
company; it now also covers which common-collection tree to check.

## Testing

Same constraint as #686: no working full build in this sandbox
(missing generated headers/deps), so the exact functions as committed
were extracted into a standalone Qt6 harness and run against the real
shipped 10_electric/qet_labels.xml (pretty-printed and minified),
a synthetic empty-prefix-override file, and the nesting-trap file
above -- 9/9, including the three cases #686 already fixed (direct
prefix, inherited prefix, not-found) staying correct, confirming this
rewrite doesn't regress that work.

Not exercised here (needs a real running QETApp / ElementsLocation,
which the standalone harness can't stand up): the elementPrefixForLocation()
candidate-list wiring itself -- collection_root computation, the
from-root/tree-relative dual lookup, and the common-then-custom-then-
company ordering. That code is mechanical and was reviewed carefully
by hand, but it has not been run.
2026-09-12 06:32:07 +12:00

872 lines
29 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "assignvariables.h"
#include "../diagram.h"
#include "../diagramposition.h"
#include "../qetapp.h"
#include "../qetgraphicsitem/conductor.h"
#include "../qetgraphicsitem/element.h"
#include "../qetxml.h"
#include "../qetproject.h"
#include <QDir>
#include <QDomDocument>
#include <QStringList>
#include <QVariant>
#include <utility>
namespace autonum
{
/**
@brief sequentialNumbers::sequentialNumbers
*/
sequentialNumbers::sequentialNumbers()
{}
sequentialNumbers::sequentialNumbers(const sequentialNumbers &other)
{
unit = other.unit;
wrap = other.wrap;
unit_folio = other.unit_folio;
ten = other.ten;
ten_folio = other.ten_folio;
hundred = other.hundred;
hundred_folio = other.hundred_folio;
alpha = other.alpha;
}
sequentialNumbers::~sequentialNumbers()
{}
sequentialNumbers &sequentialNumbers::operator=(
const sequentialNumbers &other)
{
if (&other == this || other == *this)
return (*this);
unit = other.unit;
wrap = other.wrap;
unit_folio = other.unit_folio;
ten = other.ten;
ten_folio = other.ten_folio;
hundred = other.hundred;
hundred_folio = other.hundred_folio;
alpha = other.alpha;
return (*this);
}
bool sequentialNumbers::operator==(const sequentialNumbers &other) const
{
if (unit == other.unit && \
wrap == other.wrap && \
unit_folio == other.unit_folio && \
ten == other.ten && \
ten_folio == other.ten_folio && \
hundred == other.hundred && \
hundred_folio == other.hundred_folio && \
alpha == other.alpha)
return true;
else
return false;
}
bool sequentialNumbers::operator!=(const sequentialNumbers &other) const
{
if (*this == other)
return false;
else
return true;
}
/**
@brief sequentialNumbers::toXml
export this sequential numbers into a QDomElement.
@param document : QDomDocument used to create the QDomElement
@param tag_name : the tag name used for the QDomElement.
@return A QDomElement, if this sequential have no value,
the returned QDomELement is empty
*/
QDomElement sequentialNumbers::toXml(QDomDocument &document,
const QString& tag_name) const
{
QDomElement element = document.createElement(tag_name);
if (!unit.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"unit",
unit.join(";")));
if (!wrap.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"wrap",
wrap.join(";")));
if (!unit_folio.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"unitFolio",
unit_folio.join(";")));
if(!ten.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"ten",
ten.join(";")));
if(!ten_folio.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"tenFolio",
ten_folio.join(";")));
if(!hundred.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"hundred",
hundred.join(";")));
if(!hundred_folio.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"hundredFolio",
hundred_folio.join(";")));
if(!alpha.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"alpha",
alpha.join(";")));
return element;
}
/**
@brief sequentialNumbers::fromXml
Import sequential values from a QDomElement
@param element
*/
void sequentialNumbers::fromXml(const QDomElement &element)
{
if (!element.hasChildNodes())
return;
QDomElement from;
from = element.firstChildElement("unit");
unit = from.text().split(";");
//Absent from files written before cyclic parts could be
//rendered; an empty list is the correct reading of that.
from = element.firstChildElement("wrap");
wrap = from.text().split(";");
from = element.firstChildElement("unitFolio");
unit_folio = from.text().split(";");
from = element.firstChildElement("ten");
ten = from.text().split(";");
from = element.firstChildElement("tenFolio");
ten_folio = from.text().split(";");
from = element.firstChildElement("hundred");
hundred = from.text().split(";");
from = element.firstChildElement("hundredFolio");
hundred_folio = from.text().split(";");
from = element.firstChildElement("alpha");
alpha = from.text().split(";");
}
//Clear this sequence
void sequentialNumbers::clear()
{
unit.clear();
wrap.clear();
unit_folio.clear();
ten.clear();
ten_folio.clear();
hundred.clear();
hundred_folio.clear();
alpha.clear();
}
/**
@brief AssignVariables::formulaToLabel
Return the formula with variable assigned
(ready to be displayed)
@param formula - the formula to work
@param seqStruct - struct where is stocked int values
(struct is passed as a reference
and modified by this static method)
@param diagram - the diagram where occure the formula.
@param elmt - parent element (if any) of the formula
@return the string with variable assigned.
*/
QString AssignVariables::formulaToLabel(QString formula,
sequentialNumbers &seqStruct,
Diagram *diagram,
const Element *elmt,
const Conductor *cndr)
{
AssignVariables av(std::move(formula),
seqStruct,
diagram,
elmt,
cndr);
seqStruct = av.m_seq_struct;
return av.m_assigned_label;
}
/**
@brief AssignVariables::replaceVariable
Replace the variables in formula in form %{my-var}
to the corresponding value stored in dc
@param formula
@param dc
@return
*/
QString AssignVariables::replaceVariable(const QString &formula,
const DiagramContext &dc)
{
QString str = formula;
str.replace("%{label}", dc.value("label").toString());
str.replace("%{plant}", dc.value("plant").toString());
str.replace("%{comment}", dc.value("comment").toString());
str.replace("%{description}", dc.value("description").toString());
str.replace("%{designation}", dc.value("designation").toString());
str.replace("%{manufacturer}", dc.value("manufacturer").toString());
str.replace("%{manufacturer_reference}",
dc.value("manufacturer_reference").toString());
str.replace("%{supplier}", dc.value("supplier").toString());
str.replace("%{quantity}", dc.value("quantity").toString());
str.replace("%{unity}", dc.value("unity").toString());
str.replace("%{auxiliary1}", dc.value("auxiliary1").toString());
str.replace("%{description_auxiliary1}", dc.value("description_auxiliary1").toString());
str.replace("%{designation_auxiliary1}", dc.value("designation_auxiliary1").toString());
str.replace("%{manufacturer_auxiliary1}", dc.value("manufacturer_auxiliary1").toString());
str.replace("%{manufacturer_reference_auxiliary1}", dc.value("manufacturer_reference_auxiliary1").toString());
str.replace("%{supplier_auxiliary1}", dc.value("supplier_auxiliary1").toString());
str.replace("%{quantity_auxiliary1}", dc.value("quantity_auxiliary1").toString());
str.replace("%{unity_auxiliary1}", dc.value("unity_auxiliary1").toString());
str.replace("%{auxiliary2}", dc.value("auxiliary2").toString());
str.replace("%{description_auxiliary2}", dc.value("description_auxiliary2").toString());
str.replace("%{designation_auxiliary2}", dc.value("designation_auxiliary2").toString());
str.replace("%{manufacturer_auxiliary2}", dc.value("manufacturer_auxiliary2").toString());
str.replace("%{manufacturer_reference_auxiliary2}", dc.value("manufacturer_reference_auxiliary2").toString());
str.replace("%{supplier_auxiliary2}", dc.value("supplier_auxiliary2").toString());
str.replace("%{quantity_auxiliary2}", dc.value("quantity_auxiliary2").toString());
str.replace("%{unity_auxiliary2}", dc.value("unity_auxiliary2").toString());
str.replace("%{auxiliary3}", dc.value("auxiliary3").toString());
str.replace("%{description_auxiliary3}", dc.value("description_auxiliary3").toString());
str.replace("%{designation_auxiliary3}", dc.value("designation_auxiliary3").toString());
str.replace("%{manufacturer_auxiliary3}", dc.value("manufacturer_auxiliary3").toString());
str.replace("%{manufacturer_reference_auxiliary3}", dc.value("manufacturer_reference_auxiliary3").toString());
str.replace("%{supplier_auxiliary3}", dc.value("supplier_auxiliary3").toString());
str.replace("%{quantity_auxiliary3}", dc.value("quantity_auxiliary3").toString());
str.replace("%{unity_auxiliary3}", dc.value("unity_auxiliary3").toString());
str.replace("%{auxiliary4}", dc.value("auxiliary4").toString());
str.replace("%{description_auxiliary4}", dc.value("description_auxiliary4").toString());
str.replace("%{designation_auxiliary4}", dc.value("designation_auxiliary4").toString());
str.replace("%{manufacturer_auxiliary4}", dc.value("manufacturer_auxiliary4").toString());
str.replace("%{manufacturer_reference_auxiliary4}", dc.value("manufacturer_reference_auxiliary4").toString());
str.replace("%{supplier_auxiliary4}", dc.value("supplier_auxiliary4").toString());
str.replace("%{quantity_auxiliary4}", dc.value("quantity_auxiliary4").toString());
str.replace("%{unity_auxiliary4}", dc.value("unity_auxiliary4").toString());
str.replace("%{machine_manufacturer_reference}", dc.value("machine_manufacturer_reference").toString());
str.replace("%{location}", dc.value("location").toString());
str.replace("%{function}", dc.value("function").toString());
str.replace("%{tension_protocol}", dc.value("tension_protocol").toString());
str.replace("%{conductor_section}", dc.value("conductor_section").toString());
str.replace("%{conductor_color}", dc.value("conductor_color").toString());
str.replace("%{void}", QString());
str.replace("%{plc_type}", dc.value("plc_type").toString());
str.replace("%{plc_address}", dc.value("plc_address").toString());
str.replace("%{plc_function}", dc.value("plc_function").toString());
str.replace("%{plc_comment}", dc.value("plc_comment").toString());
str.replace("%{plc_crossref}", dc.value("plc_crossref").toString());
str.replace("%{plc_tc}", dc.value("plc_tc").toString());
str.replace("%{plc_t1}", dc.value("plc_t1").toString());
str.replace("%{plc_t2}", dc.value("plc_t2").toString());
str.replace("%{plc_t3}", dc.value("plc_t3").toString());
str.replace("%{plc_t4}", dc.value("plc_t4").toString());
return str;
}
/**
* @brief AssignVariables::genericXref
* @param element
* @return a simple Xref string in form of 'folio-letterNumber'
*/
QString AssignVariables::genericXref(const Element *element)
{
QString str;
if (!element->diagram()) {
return str;
}
QSettings settings;
auto diagram = element->diagram();
if (settings.value(QLatin1String("genericpanel/folio"), true).toBool()) {
str = diagram->border_and_titleblock.finalfolio();
} else {
str = QString::number(diagram->folioIndex()+1);
}
str.append("-");
str.append(diagram->convertPosition(element->scenePos()).toString());
return str;
}
AssignVariables::AssignVariables(const QString& formula,
const sequentialNumbers& seqStruct,
Diagram *diagram,
const Element *elmt,
const Conductor *cndr):
m_diagram(diagram),
m_arg_formula(formula),
m_assigned_label(formula),
m_seq_struct(seqStruct),
m_element(elmt),
m_conductor(cndr)
{
if (m_diagram)
{
m_assigned_label.replace("%F",
m_diagram
-> border_and_titleblock
.folio());
m_assigned_label.replace("%f",
QString::number(
m_diagram
->folioIndex()+1));
m_assigned_label.replace("%id",
QString::number(
m_diagram
->folioIndex()+1));
m_assigned_label.replace("%total",
QString::number(
m_diagram
->border_and_titleblock
.folioTotal()));
m_assigned_label.replace("%M",
m_diagram
-> border_and_titleblock
.plant());
m_assigned_label.replace("%LM",
m_diagram
-> border_and_titleblock
.locmach());
QSettings settings;
if (m_element)
{
if (settings.value("border-columns_0", true).toBool()){
m_assigned_label.replace("%c", QString::number(m_diagram->convertPosition(m_element->scenePos()).number() - 1));
}else{
m_assigned_label.replace("%c", QString::number(m_diagram->convertPosition(m_element->scenePos()).number()));
}
m_assigned_label.replace("%l", m_diagram->convertPosition(m_element->scenePos()).letter());
m_assigned_label.replace("%prefix", m_element->getPrefix());
}
if (m_conductor)
{
m_assigned_label.replace("%wf", cndr->properties().m_function);
m_assigned_label.replace("%wv", cndr->properties().m_tension_protocol);
m_assigned_label.replace("%wc", cndr->properties().m_wire_color);
m_assigned_label.replace("%ws", cndr->properties().m_wire_section);
}
assignTitleBlockVar();
assignProjectVar();
assignSequence();
}
}
void AssignVariables::assignTitleBlockVar()
{
for (int i = 0; i < m_diagram->border_and_titleblock.additionalFields().count(); i++)
{
QString folio_variable = m_diagram->border_and_titleblock.additionalFields().keys().at(i);
QVariant folio_value = m_diagram->border_and_titleblock.additionalFields().operator [](folio_variable);
if (m_assigned_label.contains(folio_variable)) {
m_assigned_label.replace("%{" + folio_variable + "}", folio_value.toString());
m_assigned_label.replace("%" + folio_variable , folio_value.toString());
}
}
}
void AssignVariables::assignProjectVar()
{
for (int i = 0; i < m_diagram->project()->projectProperties().count(); i++)
{
QString folio_variable = m_diagram->project()->projectProperties().keys().at(i);
QVariant folio_value = m_diagram->project()->projectProperties().operator [](folio_variable);
if (m_assigned_label.contains(folio_variable)) {
m_assigned_label.replace("%{" + folio_variable + "}", folio_value.toString());
m_assigned_label.replace("%" + folio_variable , folio_value.toString());
}
}
}
void AssignVariables::assignSequence()
{
int max = qMax(
qMax(
qMax(m_seq_struct.unit_folio.size(),
m_seq_struct.ten_folio.size()),
qMax(m_seq_struct.hundred_folio.size(),
m_seq_struct.unit.size())),
qMax(
qMax(m_seq_struct.hundred.size(),
m_seq_struct.ten.size()),
qMax(m_seq_struct.alpha.size(),
m_seq_struct.wrap.size()))
);
for (int i=1; i<=max ; i++)
{
if (m_assigned_label.contains("%sequ_" + QString::number(i)) && m_seq_struct.unit.size() >= i) {
m_assigned_label.replace("%sequ_" + QString::number(i),m_seq_struct.unit.at(i-1));
}
if (m_assigned_label.contains("%seqw_" + QString::number(i)) && m_seq_struct.wrap.size() >= i) {
m_assigned_label.replace("%seqw_" + QString::number(i),m_seq_struct.wrap.at(i-1));
}
if (m_assigned_label.contains("%seqt_" + QString::number(i)) && m_seq_struct.ten.size() >= i) {
m_assigned_label.replace("%seqt_" + QString::number(i),m_seq_struct.ten.at(i-1));
}
if (m_assigned_label.contains("%seqh_" + QString::number(i)) && m_seq_struct.hundred.size() >= i) {
m_assigned_label.replace("%seqh_" + QString::number(i),m_seq_struct.hundred.at(i-1));
}
if (m_assigned_label.contains("%sequf_" + QString::number(i)) && m_seq_struct.unit_folio.size() >= i) {
m_assigned_label.replace("%sequf_" + QString::number(i),m_seq_struct.unit_folio.at(i-1));
}
if (m_assigned_label.contains("%seqtf_" + QString::number(i)) && m_seq_struct.ten_folio.size() >= i) {
m_assigned_label.replace("%seqtf_" + QString::number(i),m_seq_struct.ten_folio.at(i-1));
}
if (m_assigned_label.contains("%seqhf_" + QString::number(i)) && m_seq_struct.hundred_folio.size() >= i) {
m_assigned_label.replace("%seqhf_" + QString::number(i),m_seq_struct.hundred_folio.at(i-1));
}
if (m_assigned_label.contains("%seqa_" + QString::number(i)) && m_seq_struct.alpha.size() >= i) {
m_assigned_label.replace("%seqa_" + QString::number(i),m_seq_struct.alpha.at(i-1));
}
}
}
/**
@brief setSequentialToList
Append all sequential of type type owned by context in list
@param list : list to have value inserted
@param context : numerotation context to retrieve value
@param type : type of sequential
(unit, unitfolio, ten, tenfolio, hundred, hundredfolio)
*/
void setSequentialToList(QStringList &list,
NumerotationContext &context,
const QString& type)
{
for (int i = 0; i < context.size(); i++)
{
if (context.itemAt(i).at(0) == type)
{
const QStringList item = context.itemAt(i);
//A zero-padding mask, spreadsheet style: its length is the
//minimum number of digits. It overrides the width implied
//by the part type, so "Chiffre 01" with a mask of "0000"
//pads to four. An absent mask -- which is every context
//written before the field existed -- falls through to the
//type's own width, so nothing about existing projects
//changes.
const QString mask = NumerotationContext::formatOf(item);
QString number;
if (type == "alpha")
//Alphabetic value, not an integer -- used as-is.
number = item.at(1);
else if (!mask.isEmpty())
number = QString("%1").arg(item.at(1).toInt(), mask.length(), 10, QChar('0'));
else if (type == "ten" || type == "tenfolio")
number = QString("%1").arg(item.at(1).toInt(), 2, 10, QChar('0'));
else if (type == "hundred" || type == "hundredfolio")
number = QString("%1").arg(item.at(1).toInt(), 3, 10, QChar('0'));
else number = QString::number(item.at(1).toInt());
list.append(number);
}
}
}
/**
@brief setFolioSequentialToHash
Insert all value of list in hash with key autoNumName
@param list : list to get values from
@param hash : hash to have values inserted
@param autoNumName : name to use as key of hash
*/
void setFolioSequentialToHash(QStringList &list,
QHash<QString,
QStringList> &hash,
const QString& autoNumName)
{
if (hash.isEmpty() || !hash.contains(autoNumName))
{
QStringList max;
for (int i = 0; i < list.size(); i++)
{
max.append(list.at(i));
}
hash.insert(autoNumName,max);
}
else if (hash.contains(autoNumName))
{
//Load the String List and update it
QStringList max = hash.value(autoNumName);
for (int i = 0; i < list.size(); i++)
{
if ((list.at(i).toInt()) > max.at(i).toInt())
{
max.replace(i,list.at(i));
hash.remove(autoNumName);
hash.insert(autoNumName,max);
}
}
}
}
/**
@brief setSequential
Fill seqStruct
@param label : label of sequential to fill
(%sequ_, %sequf_, %seqt_, ect....)
@param seqStruct : struct to fill
@param context : numerotation context use
to know the current sequential num.
@param diagram : diagram where the sequential occur,
notably use when label is folio type
(%sequf_, %seqtf_, %seqhf_),
to keep up to date the current sequential of folio.
@param hashKey :
the hash key used to store the sequential for folio type.
*/
void setSequential(const QString& label, sequentialNumbers &seqStruct, NumerotationContext &context, Diagram *diagram, const QString& hashKey)
{
if (!context.isEmpty())
{
if (label.contains("%sequ_"))
{
autonum::setSequentialToList(seqStruct.unit, context,"unit");
}
if (label.contains("%seqw_"))
{
autonum::setSequentialToList(seqStruct.wrap, context,"wrap");
}
if (label.contains("%sequf_"))
{
autonum::setSequentialToList(seqStruct.unit_folio, context,"unitfolio");
autonum::setFolioSequentialToHash(seqStruct.unit_folio, diagram->m_elmt_unitfolio_max, hashKey);
}
if (label.contains("%seqt_"))
{
autonum::setSequentialToList(seqStruct.ten, context,"ten");
}
if (label.contains("%seqtf_"))
{
autonum::setSequentialToList(seqStruct.ten_folio, context,"tenfolio");
autonum::setFolioSequentialToHash(seqStruct.ten_folio, diagram->m_elmt_tenfolio_max, hashKey);
}
if (label.contains("%seqh_"))
{
autonum::setSequentialToList(seqStruct.hundred, context,"hundred");
}
if (label.contains("%seqhf_"))
{
autonum::setSequentialToList(seqStruct.hundred_folio, context,"hundredfolio");
autonum::setFolioSequentialToHash(seqStruct.hundred_folio, diagram->m_elmt_hundredfolio_max, hashKey);
}
if (label.contains("%seqa_"))
{
autonum::setSequentialToList(seqStruct.alpha, context,"alpha");
}
}
}
/**
@brief numerotationContextToFormula
@param nc
@return the numerotation context, converted to formula
*/
QString numerotationContextToFormula(const NumerotationContext &nc)
{
QString type;
QString value;
QString formula;
int count_unit = 0;
int count_wrap = 0;
int count_unitf = 0;
int count_ten = 0;
int count_tenf = 0;
int count_hundred = 0;
int count_hundredf = 0;
int count_alpha = 0;
for(int i=0 ; i<nc.size() ; i++)
{
type = nc.itemAt(i).at(0);
value = nc.itemAt(i).at(1);
if (type == "idfolio") {
formula.append("%id");
}
else if (type == "folio") {
formula.append("%F");
}
else if (type == "plant") {
formula.append("%M");
}
else if (type == "locmach") {
formula.append("%LM");
}
else if (type == "elementcolumn") {
formula.append("%c");
}
else if (type == "elementline") {
formula.append("%l");
}
else if (type == "elementprefix") {
formula.append("%prefix");
}
else if (type == "string") {
formula.append(value);
}
else if (type == "unit") {
count_unit++;
formula.append("%sequ_" + QString::number(count_unit));
}
else if (type == "wrap") {
count_wrap++;
formula.append("%seqw_" + QString::number(count_wrap));
}
else if (type == "unitfolio") {
count_unitf++;
formula.append("%sequf_" + QString::number(count_unitf));
}
else if (type == "ten") {
count_ten++;
formula.append("%seqt_" + QString::number(count_ten));
}
else if (type == "tenfolio") {
count_tenf++;
formula.append("%seqtf_" + QString::number(count_tenf));
}
else if (type == "hundred") {
count_hundred++;
formula.append("%seqh_" + QString::number(count_hundred));
}
else if (type == "hundredfolio") {
count_hundredf++;
formula.append("%seqhf_" + QString::number(count_hundredf));
}
else if (type == "alpha") {
count_alpha++;
formula.append("%seqa_" + QString::number(count_alpha));
}
}
return formula;
}
/**
@brief prefixFromLabelFile
Look up a prefix for @a path (path[dirLevel] outermost, path[1] the
deepest directory; path[0], the element's own file name, is never
matched) in the qet_labels.xml at @a filepath.
Descends through nested \<category name="..."\> elements matching
path[dirLevel], path[dirLevel-1], ..., path[1] in turn, considering
only *direct* children at each step -- unlike a flat token scan,
this cannot be fooled by a same-named category living elsewhere in
the document at the wrong nesting depth (bugtracker #671 item 5).
At each matched level, that category's own \<prefix\> child -- even
an empty one -- overrides whatever a shallower ancestor already
provided, so an explicit empty \<prefix/\> cancels inheritance
rather than silently falling back to it (the behaviour requested in
PR #686 review). A category with no \<prefix\> child at all leaves
the inherited value untouched, which is how a directory with no
prefix of its own comes to inherit its parent's, as the file's own
header comment documents.
@return the prefix that applies, or a null QString if the file
cannot be read, is not well-formed, or does not describe this
path at all (as opposed to describing it with no prefix
anywhere along it, which is a non-null empty string).
*/
static QString prefixFromLabelFile(const QString &filepath, const QStringList &path, int dirLevel)
{
QFile file(filepath);
if (!file.open(QFile::ReadOnly | QFile::Text))
return QString();
QDomDocument document;
if (!document.setContent(&file))
return QString();
QDomElement node = document.documentElement();
if (node.isNull())
return QString();
QString prefix;
for (int i = dirLevel ; i >= 1 ; --i) {
QDomElement child = node.firstChildElement(QStringLiteral("category"));
while (!child.isNull()
&& child.attribute(QStringLiteral("name")) != path[i]) {
child = child.nextSiblingElement(QStringLiteral("category"));
}
if (child.isNull())
return QString();
node = child;
const QDomElement own = node.firstChildElement(QStringLiteral("prefix"));
if (!own.isNull()) {
//readElementText()'s null-vs-empty distinction that PR
//#686 needed for the old QXmlStreamReader-based lookup
//has a QDomElement equivalent: text() on an empty
//element can itself come back null depending on how the
//XML was written, so the same explicit fallback applies
//-- an empty QString here means "found, deliberately
//blank", not "not found".
prefix = own.text();
if (prefix.isNull())
prefix = QString("");
}
}
return prefix;
}
/**
@brief elementPrefixForLocation
@param location
@return the prefix for an element represented by location,
prefix can be null.
Search for a prefix only if location represent
an element embedded in a project
*/
QString elementPrefixForLocation(const ElementsLocation &location)
{
if (!location.isProject())
return QString();
//Directory names from the element up to (not including) the
//collection root, outermost last -- path[dirLevel] is the
//top-level category, path[1] the element's immediate parent
//directory, path[0] the element's own file name (never matched
//against a category: the search stops descending once it has
//matched path[1], the deepest real directory). An unbounded
//QStringList rather than a fixed-size array, because a custom
//collection can nest deeper than the shipped one -- see
//bugtracker #671 item 3.
QStringList path;
ElementsLocation current_location = location;
while ((current_location.parent() != current_location)
&& (current_location.parent().fileName() != "import"))
{
path << current_location.fileName();
current_location = current_location.parent();
}
//User element without folder treatment
if (path.isEmpty()) {
path << current_location.fileName();
current_location = current_location.parent();
}
const int dirLevel = path.size() - 1;
//Name of the top-level tree the element's path was found
//under, e.g. "10_electric" -- or, for a custom/company
//collection not organised that way, whatever its top-level
//folder happens to be called.
const QString collection_root = current_location.fileName();
//Every top-level common-collection tree (10_electric,
//20_logic, 30_hydraulic, ...) may carry its own
//qet_labels.xml, with categories relative to that tree, the
//same way 10_electric/qet_labels.xml already does -- not just
//10_electric, which is all the hardcoded check this replaces
//used to allow (bugtracker #671 item 2). commonElementsDir()
//-- unlike customElementsDir(), which normalises this itself
//-- returns whatever path the user configured verbatim, with
//no guaranteed trailing separator; concatenating a suffix onto
//it directly used to silently mangle the path (and so the
//prefix lookup) for any install relocated to a directory
//without a trailing slash (#671 item 1). QDir::filePath()
//joins correctly either way.
{
const QString common_file = QDir(QETApp::commonElementsDir())
.filePath(collection_root + QStringLiteral("/qet_labels.xml"));
const QString prefix = prefixFromLabelFile(common_file, path, dirLevel);
if (!prefix.isNull()) {
return prefix;
}
}
/* Which collection an element actually came from is not
* recoverable post-import (addElement() strips the protocol),
* so custom and company labels files are tried against two
* possible layouts: with the collection's top-level tree name
* folded into the path (a custom/company file organised as a
* mirror of the common collection, tree name included) and
* without it (a file scoped to just this one tree, matching
* how the common collection's own files are written). Custom
* is tried before company, so a user override wins over a
* shared one.
*/
QStringList path_from_root = path;
path_from_root << collection_root;
const QStringList candidate_dirs = {
QETApp::customElementsDir(),
QETApp::companyElementsDir()
};
for (const QString &dir : candidate_dirs) {
const QString candidate =
QDir(dir).filePath(QStringLiteral("qet_labels.xml"));
for (const QStringList &segments : {path_from_root, path}) {
const QString prefix = prefixFromLabelFile(
candidate, segments, segments.size() - 1);
if (!prefix.isNull()) {
return prefix;
}
}
}
return QString();
}
}