mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-25 03:04:13 +02:00
7c0e867226
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.
872 lines
29 KiB
C++
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();
|
|
}
|
|
}
|