OOFEM 3.0
Loading...
Searching...
No Matches
xmldatareader.C
Go to the documentation of this file.
1/*
2 *
3 * ##### ##### ###### ###### ### ###
4 * ## ## ## ## ## ## ## ### ##
5 * ## ## ## ## #### #### ## # ##
6 * ## ## ## ## ## ## ## ##
7 * ## ## ## ## ## ## ## ##
8 * ##### ##### ## ###### ## ##
9 *
10 *
11 * OOFEM : Object Oriented Finite Element Code
12 *
13 * Copyright (C) 1993 - 2025 Borek Patzak
14 *
15 *
16 *
17 * Czech Technical University, Faculty of Civil Engineering,
18 * Department of Structural Mechanics, 166 29 Prague, Czech Republic
19 *
20 * This library is free software; you can redistribute it and/or
21 * modify it under the terms of the GNU Lesser General Public
22 * License as published by the Free Software Foundation; either
23 * version 2.1 of the License, or (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 * Lesser General Public License for more details.
29 *
30 * You should have received a copy of the GNU Lesser General Public
31 * License along with this library; if not, write to the Free Software
32 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
33 */
34
35#include "xmldatareader.h"
36#include "error.h"
37
38#include <string>
39#include <filesystem>
40#include <fstream>
41#include <sstream>
42#include <limits>
43#include <cstring>
44#include <filesystem>
45#include <pugixml.hpp>
46#include <bits/stdc++.h>
47#include <string.h>
48
49#ifdef _MSC_VER
50#define strcasecmp _stricmp
51#endif
52
53
54// #define _XML_DEBUG(m) std::cerr<<std::string(__FUNCTION__).substr(0,20)<<": "<<m<<std::endl;
55#define _XML_DEBUG(m)
56
57
58namespace oofem {
59 pugi::xml_document& XMLDataReader::loadXml(pugi::xml_node parent, const std::string& xml_){
60 std::filesystem::path xml=(xml_);
61 if(xml.is_relative() && parent){
62 std::filesystem::path parentXml(xmlFiles[parent.root()]);
63 xml=parentXml.parent_path()/xml;
64 _XML_DEBUG("Relative path "<<xml_<<" resolved to "<<xml<<" (parent is "<<parentXml<<")");
65 }
66 // trasverse just to get line breaks
67 std::ifstream i(xml,std::ifstream::in|std::ios::binary);
68 if(!i.is_open()) OOFEM_ERROR("Error opening %s: %s",xml.c_str(),std::strerror(errno));
69 std::list<size_t> br;
70 size_t pos=0;
71 while(i.good()){ char c=i.get(); pos++; if(c=='\n') br.push_back(pos); }
72 std::vector<size_t> nl;
73 nl.reserve(br.size());
74 #pragma GCC diagnostic push
75 #pragma GCC diagnostic ignored "-Waggressive-loop-optimizations"
76 nl.assign(br.begin(),br.end());
77 #pragma GCC diagnostic pop
78 _XML_DEBUG(xml<<": "<< nl.size()<<" newlines found, about to parse the XML...");
79 docs[parent]=pugi::xml_document();
80 if(parent) XMLInputRecord::node_seen_set(parent,true);
81 auto& doc=docs[parent];
82 pugi::xml_parse_result result=doc.load_file(xml.c_str());
83 if(!result){
84 auto [line,col]=offset2lc(nl,result.offset);
85 OOFEM_ERROR("Error parsing %s:%ld:%ld: %s",xml.c_str(),line,col,result.description());
86 }
87 newlines[doc]=std::move(nl);
88 xmlFiles[doc]=xml;
89 size_t nChildren=std::distance(doc.begin(),doc.end());
90 if(nChildren!=1) OOFEM_ERROR("Error reading %s: must have exactly 1 top-level tag (not %ld)",xml.c_str(),nChildren);
91 _XML_DEBUG(xml<<": "<<" parsing done, root is '"<<doc.first_child().name()<<"'");
92 return doc;
93 }
94
95 pugi::xml_node XMLDataReader::resolveXiInclude(const pugi::xml_node& n){
96 if(n.name()!=XiIncludeTag) OOFEM_ERROR("Error resolving %s: must be xi:include (not %s)",loc().c_str(),n.name());
97 if(auto xi=docs.find(n); xi!=docs.end()) return xi->second.first_child();
98 pugi::xml_attribute href=n.attribute("href");
99 if(!href) OOFEM_ERROR("%s: xi:include must have 'href' attribute.",loc().c_str());
100 _XML_DEBUG(loc()<<":"<<giveStackPath()<<": reading xi:include, href='"<<href.value()<<"'");
101 return loadXml(n,href.value()).first_child();
102 }
103
104 pugi::xml_node XMLDataReader::giveNamedChild(const pugi::xml_node& parent, const std::string& name){
105 _XML_DEBUG("finding child named '"<<name<<"'");
106 for(pugi::xml_node c=parent.first_child(); c; c=c.next_sibling()){
107 if(c.name()==name){
108 _XML_DEBUG("returning direct child "<<loc(c));
109 return c;
110 }
111 if(strcasecmp(c.name(),name.c_str())==0){
112 std::cerr<<loc(parent)<<": case-insensitive XML tag match ('"<<c.name()<<"', requested '"<<name<<"')"<<std::endl;
113 break;
114 }
115 if(c.name()==XiIncludeTag){
116 pugi::xml_node xi=resolveXiInclude(c);
117 if(xi.name()==name){
118 _XML_DEBUG("returning xi:include "<<loc(c));
119 return xi;
120 }
121 }
122 }
123 return pugi::xml_node();
124 }
125
126 XMLDataReader::XMLDataReader(const std::string& xmlFile_): topXmlFile(xmlFile_) {
127 pugi::xml_document& doc=loadXml(pugi::xml_node(),topXmlFile);
128 stack.push_back(StackItem{doc,doc});
129 enterGroup("oofem");
130 pugi::xml_node root=stack.back().parent;
131 pugi::xml_node output_node=root.child("Output"), description_node=root.child("Description");
132 if(!output_node) OOFEM_ERROR("Error reading %s: <Output> node missing in %s.",topXmlFile.c_str(),root.name());
133 if(!description_node) OOFEM_ERROR("Error reading %s: <Description> node missing.",topXmlFile.c_str());
134 XMLInputRecord::node_seen_set(output_node,true);
135 XMLInputRecord::node_seen_set(description_node,true);
136 outputFileName=output_node.text().as_string();
137 description=description_node.text().as_string();
138 topRecord=std::shared_ptr<InputRecord>(new XMLInputRecord(this,root));
139 }
140
141 bool XMLDataReader :: canRead(const std::string& xml){
142 return std::filesystem::path(xml).extension() == ".xml";
143 }
144
145 std::tuple<size_t,size_t> XMLDataReader::offset2lc(const std::vector<size_t>& nl, size_t offset) const {
146 if(nl.empty()) return std::make_tuple(0,0);
147 size_t ix=std::distance(nl.begin(),std::lower_bound(nl.begin(),nl.end(),offset));
148 // _XML_DEBUG("offset="<<offset<<",ix="<<ix); // comment out so that it does not damage most other _XML_DEBUG messages
149 return std::make_tuple(ix+1,offset-(ix==0?0:nl[ix-1]));
150 }
151 std::string XMLDataReader::loc() const { return loc(stack.back().curr?stack.back().curr:stack.back().parent); }
152 std::string XMLDataReader::loc(const pugi::xml_node& n) const {
153 std::ostringstream oss;
154 auto [line,col]=offset2lc(newlines.at(n.root()),n.offset_debug());
155 oss<<xmlFiles.at(n.root())<<":"<<line<<":"<<col;
156 return oss.str();
157 }
158
159 int XMLDataReader::giveGroupCount(const std::string& name) {
160 if(name.empty()) return giveCurrentGroupCount();
161 enterGroup(name);
162 int ret=this->giveCurrentGroupCount();
163 leaveGroup(name);
164 _XML_DEBUG(giveStackPath()<<" // "<<name<<": size is "<<ret);
165 return ret;
166 }
167
169 int ret=0;
170 for([[maybe_unused]] pugi::xml_node n: stack.back().parent.children()) ret++;
171 _XML_DEBUG(giveStackPath()<<": size is "<<ret);
172 return ret;
173 }
174
176 std::ostringstream oss;
177 for(const StackItem& i: stack) oss<<"/"<<i.parent.name();
178 return oss.str();
179 }
180 void XMLDataReader::enterGroup(const std::string& name) {
181 _XML_DEBUG("::"<<giveStackPath()<<": enter '"<<name<<"'");
182 pugi::xml_node grp=giveNamedChild(stack.back().parent,name);
184 if(!grp){ std::cerr<<"No "<<giveStackPath()<<" // "<<name<<std::endl; OOFEM_ERROR("Error reading %s: %s has no child %s",loc(grp).c_str(),giveStackPath().c_str(),name.c_str()); }
185 // stack.back().seen.insert(grp);
186 pugi::xml_node ch1=grp.first_child();
187 if(!ch1) OOFEM_ERROR("Error reading %s: %s: %s has no child nodes",loc().c_str(),giveStackPath().c_str(),name.c_str());
188 stack.push_back(StackItem{grp,ch1});
189 }
190 void XMLDataReader::leaveGroup(const std::string& name) {
191 _XML_DEBUG(loc()<<"::"<<giveStackPath()<<": leave "<<name);
192 // if an exception is being propagated, this error would hide it
193 if(!std::uncaught_exceptions()){
194 if(stack.back().parent.name()!=name) OOFEM_ERROR("Error reading %s: %s: bottom-most group is %s, request to leave %s.",loc().c_str(),giveStackPath().c_str(),stack.back().parent.name(),name.c_str());
195 }
196 stack.pop_back();
197 }
199 _XML_DEBUG(loc()<<"::"<<giveStackPath());
200 XMLInputRecord* r=dynamic_cast<XMLInputRecord*>(rec);
201 if(!r) OOFEM_ERROR("Error reading %s: input record is not a XMLInputRecord?",loc().c_str());
202 _XML_DEBUG(" entering '"<<r->node.name()<<"' @ "<<(void*)(&r->node));
203 stack.push_back(StackItem{r->node,r->node.first_child()});
204 }
206 _XML_DEBUG(loc()<<"::"<<giveStackPath());
207 XMLInputRecord* r=dynamic_cast<XMLInputRecord*>(rec);
208 if(!r) OOFEM_ERROR("Error reading %s: input record is not a XMLInputRecord?",loc().c_str());
209 _XML_DEBUG(" leaving '"<<r->node.name()<<"' @ "<<(void*)(&r->node));
210 // if an exception is being propagated, and enterRecord/leaveRecord were not used via RAII-guard (RecordGuard),
211 // our would abort immediately (with misleading error message) and the exception would never make it to the handler
212 if(!std::uncaught_exceptions()){
213 if(r->node!=stack.back().parent) OOFEM_ERROR("Error reading %s: %s: bottom-most node is %s @ %p, request to leave %s @ %p",loc().c_str(),giveStackPath().c_str(),stack.back().parent.name(),(void*)&(stack.back().parent),r->node.name(),(void*)&(r->node));
214 }
215 stack.pop_back();
216 }
217
219 leaveGroup("oofem");
220 // doc.print(std::cerr," ");
221 if(stack.size()>1) OOFEM_WARNING("XML stack not popped (%ld entries)",stack.size());
222 pugi::xml_node n;
223 for(const auto& [xml,doc]: docs){
224 while((n=doc.find_node([](const pugi::xml_node& n)->bool{ return !XMLInputRecord::node_seen_get(n); }))){
225 // handle Output and Description nodes (PCDATA), provided the parent tag was __seen (PCDATA node cannot be assigned attribute)
226 if(n.type()==pugi::xml_node_type::node_pcdata && XMLInputRecord::node_seen_get(n.parent())){ n.parent().remove_child(n); continue; }
227 std::ostringstream oss;
228 oss<<"Unprocessed XML fragment "<<loc(n)<<"\n";
229 n.print(oss," ");
230 OOFEM_WARNING("%s",oss.str().c_str());
231 n.parent().remove_child(n);
232 }
233 }
234 }
235
237 XMLDataReader :: giveInputRecord(InputRecordType typeId, int recordId)
238 {
239 std::string tag=DataReader::InputRecordTags[typeId];
240 _XML_DEBUG(loc()<<"::"<<giveStackPath()<<": tag '"<<tag<<"'");
241 StackItem& tip(stack.back());
242 if(tag.empty()){
243 tip.curr=tip.parent.first_child();
244 while(tip.curr && tip.seen.count(tip.curr)>0){
245 _XML_DEBUG(" --- "<<tip.curr.name());
246 tip.curr=tip.curr.next_sibling();
247 }
248 if(!tip.curr) OOFEM_ERROR("Error reading %s: %s: no more entries to read (arbitrary tag).",loc().c_str(),giveStackPath().c_str());
249 } else {
250 tip.curr=tip.parent.first_child();
251 while(true){
252 if(!tip.curr) break;
253 if(tip.seen.count(tip.curr)==0){
254 if(tip.curr.name()==XiIncludeTag && resolveXiInclude(tip.curr).name()==tag) break;
255 else if(tip.curr.name()==tag) break;
256 else if(strcasecmp(tip.curr.name(),tag.c_str())==0){
257 std::cerr<<loc(tip.curr)<<": case-insensitive XML tag match ('"<<tip.curr.name()<<"', requested '"<<tag<<"')"<<std::endl;
258 break;
259 }
260 }
261 tip.curr=tip.curr.next_sibling();
262 }
263 if(!tip.curr) OOFEM_ERROR("Error reading %s: %s: no tag %s left.",loc().c_str(),giveStackPath().c_str(),tag.c_str());
264 }
265 _XML_DEBUG(" ==> "<<tip.curr.name());
266 tip.seen.insert(tip.curr);
267 pugi::xml_node n=((tip.curr.name()==XiIncludeTag) ? resolveXiInclude(tip.curr) : tip.curr);
268 // empty tag means all nodes are traversed; in that case it is plus minus safe to assume that previous record was already processed
269 if(tip.lastRecord && tag.empty()) tip.lastRecord->finish(); //for checking everything has been read before moving onto the next one
270 tip.lastRecord=std::make_shared<XMLInputRecord>(this,n);
271 tip.lastRecId=tip.lastRecord->setRecId(tip.lastRecId);
272 _XML_DEBUG(" tip.curr="<<tip.curr.name()<<": "<<XMLInputRecord::node_seen_get(tip.curr));
273 return *tip.lastRecord;
274 }
275} // end namespace oofem
std::string outputFileName
Output file name (first line in OOFEM input files).
Definition datareader.h:58
InputRecordType
Determines the type of input record.
Definition datareader.h:64
std::string description
Description line (second line in OOFEM input files).
Definition datareader.h:60
static constexpr const char * InputRecordTags[]
Definition datareader.h:76
std::vector< StackItem > stack
void enterGroup(const std::string &name) override
pugi::xml_node giveNamedChild(const pugi::xml_node &parent, const std::string &name)
pugi::xml_document & loadXml(pugi::xml_node parent, const std::string &xml)
void finish() override
std::map< pugi::xml_node, std::string > xmlFiles
int giveGroupCount(const std::string &name) override
void leaveRecord(InputRecord *) override
std::string loc() const
std::map< pugi::xml_node, pugi::xml_document > docs
std::tuple< size_t, size_t > offset2lc(const std::vector< size_t > &nl, size_t offset) const
const std::string XiIncludeTag
std::string giveStackPath()
std::shared_ptr< InputRecord > topRecord
std::map< pugi::xml_node, std::vector< size_t > > newlines
void enterRecord(InputRecord *) override
void leaveGroup(const std::string &name) override
pugi::xml_node resolveXiInclude(const pugi::xml_node &n)
XMLDataReader(const std::string &xmlFile)
static void node_seen_set(pugi::xml_node &n, bool seen)
static bool node_seen_get(const pugi::xml_node &n)
#define OOFEM_WARNING(...)
Definition error.h:80
#define OOFEM_ERROR(...)
Definition error.h:79
std::shared_ptr< XMLInputRecord > lastRecord
std::set< pugi::xml_node > seen
#define _XML_DEBUG(m)

This page is part of the OOFEM-3.0 documentation. Copyright Copyright (C) 1994-2025 Borek Patzak Bořek Patzák
Project e-mail: oofem@fsv.cvut.cz
Generated at for OOFEM by doxygen 1.15.0 written by Dimitri van Heesch, © 1997-2011