xmlf_parse.f90 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. ! xmlparse.f90 - Simple, limited XML parser in Fortran
  2. !
  3. ! $Id: xmlparse.f90,v 1.14 2007/12/07 10:10:19 arjenmarkus Exp $
  4. !
  5. ! Arjen Markus
  6. !
  7. ! General information:
  8. ! The module reads XML files by:
  9. ! - Identifying the tag and all attributes and data belonging
  10. ! to the tag.
  11. ! - Returning to the calling subprogram to let it take care of
  12. ! the tag, attributes and data.
  13. ! - If the tag is actually an ending tag, then this is flagged
  14. ! too.
  15. ! - Handling all the data is left to the calling subprogram,
  16. ! the module merely facilitates in the parsing.
  17. !
  18. ! Note:
  19. ! The module in its current version has a number of limitations:
  20. ! - It does not handle escape sequences (like &gt. to signify
  21. ! a ">" sign)
  22. ! - It does not handle tags with attributes that are spread
  23. ! over more than one line
  24. ! - The maximum length of a line is 1000 characters
  25. ! - It may report too many lines of data (empty lines)
  26. ! - No DOM support nor support for an object tree
  27. ! - It is probably not very robust in detecting malformed XML files
  28. !
  29. ! Some questions:
  30. ! - What to do with leading blanks?
  31. !
  32. ! Update - several ideas:
  33. ! - Introduce at least two options (via xml_options):
  34. ! - ignore_whitespace - remove leading blanks and leading and trailing
  35. ! empty lines from the PCDATA
  36. ! - no_data_truncation - consider truncation of data (more
  37. ! attributes or lines of character data than
  38. ! can be stored) a read error
  39. ! - Introduce convenience functions and subroutines:
  40. ! - xml_ok() - all is well, reading can continue
  41. ! - xml_data_trunc() - was there truncation of the data?
  42. ! - xml_find_attrib() - find an attribute by name
  43. !
  44. ! Further ideas:
  45. ! - simple checking via a table: parent, tag, id, min, max
  46. !
  47. module xmlf_parse
  48. implicit none
  49. integer, parameter :: XML_BUFFER_LENGTH = 1000
  50. !
  51. ! Define the data type that holds the parser information
  52. !
  53. type XML_PARSE
  54. integer :: lun ! LU-number of the XML-file
  55. integer :: level ! Indentation level (output)
  56. integer :: lineno ! Line in file
  57. logical :: ignore_whitespace ! Ignore leading blanks etc.
  58. logical :: no_data_truncation ! Do not allow data truncation
  59. logical :: too_many_attribs ! More attributes than could be stored?
  60. logical :: too_many_data ! More lines of data than could be stored?
  61. logical :: eof ! End of file?
  62. logical :: error ! Invalid XML file or other error?
  63. character(len=XML_BUFFER_LENGTH) :: line ! Buffer
  64. end type XML_PARSE
  65. !
  66. ! Global options
  67. !
  68. integer, parameter :: XML_STDOUT = -1
  69. integer, private :: report_lun_ = XML_STDOUT
  70. logical, private :: report_errors_ = .false.
  71. logical, private :: report_details_ = .false.
  72. !
  73. ! Global data (the ampersand must come first)
  74. !
  75. character(len=10), dimension(2,3), save, private :: entities = &
  76. reshape( (/ '& ', '&', &
  77. '> ', '> ', &
  78. '< ', '&lt; ' /), (/2,3/) )
  79. !
  80. ! Auxiliary routines - private
  81. private :: xml_compress_
  82. private :: xml_put_open_tag_
  83. private :: xml_put_element_
  84. private :: xml_put_close_tag_
  85. private :: xml_replace_entities_
  86. !
  87. ! Interfaces to reporting routines
  88. !
  89. private :: xml_report_details_int_
  90. private :: xml_report_details_string_
  91. private :: xml_report_errors_int_
  92. private :: xml_report_errors_string_
  93. interface xml_report_details
  94. module procedure xml_report_details_int_
  95. module procedure xml_report_details_string_
  96. end interface
  97. interface xml_report_errors
  98. module procedure xml_report_errors_int_
  99. module procedure xml_report_errors_string_
  100. module procedure xml_report_errors_extern_
  101. end interface
  102. contains
  103. ! xml_report_details_int_ --
  104. ! Routine to write a text with an integer value
  105. ! Arguments:
  106. ! text Text to be written
  107. ! int Integer value to be added
  108. !
  109. subroutine xml_report_details_int_( text, int )
  110. character(len=*), intent(in) :: text
  111. integer, intent(in) :: int
  112. if ( report_details_ ) then
  113. if ( report_lun_ .eq. XML_STDOUT ) then
  114. write (*,*) 'XMLF - ERROR - ', trim(text), int
  115. else
  116. write (report_lun_,*) 'XMLF - ERROR - ', trim(text), int
  117. endif
  118. endif
  119. end subroutine xml_report_details_int_
  120. ! xml_report_details_string_ --
  121. ! Routine to write a text with a string value
  122. ! Arguments:
  123. ! text Text to be written
  124. ! string String to be added
  125. !
  126. subroutine xml_report_details_string_( text, string )
  127. character(len=*), intent(in) :: text
  128. character(len=*), intent(in) :: string
  129. if ( report_details_ ) then
  130. if ( report_lun_ .eq. XML_STDOUT ) then
  131. write (*,*) 'XMLF - ERROR - ', trim(text), ' ', trim(string)
  132. else
  133. write (report_lun_,*) 'XMLF - ERROR - ', trim(text), ' ', trim(string)
  134. endif
  135. endif
  136. end subroutine xml_report_details_string_
  137. ! xml_report_errors_string_ --
  138. ! Routine to write an error message text with an integer value
  139. ! Arguments:
  140. ! text Text to be written
  141. ! int Integer value to be added
  142. ! lineno Line number in the file
  143. !
  144. subroutine xml_report_errors_int_( text, int, lineno )
  145. character(len=*), intent(in) :: text
  146. integer, intent(in) :: int
  147. integer, optional, intent(in) :: lineno
  148. if ( report_errors_ .or. report_details_ ) then
  149. if ( report_lun_ .eq. XML_STDOUT ) then
  150. write (*,*) 'XMLF - ERROR - ', trim(text), int
  151. if ( present(lineno) ) then
  152. write(*,*) ' At or near line', lineno
  153. endif
  154. else
  155. write (report_lun_,*) 'XMLF - ERROR - ', trim(text), int
  156. if ( present(lineno) ) then
  157. write(report_lun_,*) ' At or near line', lineno
  158. endif
  159. endif
  160. endif
  161. end subroutine xml_report_errors_int_
  162. ! xml_report_errors_string_ --
  163. ! Routine to write an error message text with a string value
  164. ! Arguments:
  165. ! text Text to be written
  166. ! string String to be added
  167. ! lineno Line number in the file
  168. !
  169. subroutine xml_report_errors_string_( text, string, lineno )
  170. character(len=*), intent(in) :: text
  171. character(len=*), intent(in) :: string
  172. integer, optional, intent(in) :: lineno
  173. if ( report_errors_ .or. report_details_ ) then
  174. if ( report_lun_ .eq. XML_STDOUT ) then
  175. write (*,*) 'XMLF - ERROR - ', trim(text), ' ', trim(string)
  176. if ( present(lineno) ) then
  177. write(*,*) ' At or near line', lineno
  178. endif
  179. else
  180. write (report_lun_,*) 'XMLF - ERROR - ', trim(text), ' ', trim(string)
  181. if ( present(lineno) ) then
  182. write(report_lun_,*) ' At or near line', lineno
  183. endif
  184. endif
  185. endif
  186. end subroutine xml_report_errors_string_
  187. ! xml_report_errors_extern_ --
  188. ! Routine to write an error message text with a string value
  189. ! Arguments:
  190. ! info Structure holding information on the XML-file
  191. ! text Text to be written
  192. ! Note:
  193. ! This routine is meant for use by routines outside
  194. ! this module
  195. !
  196. subroutine xml_report_errors_extern_( info, text )
  197. type(XML_PARSE), intent(in) :: info
  198. character(len=*), intent(in) :: text
  199. if ( report_lun_ .eq. XML_STDOUT ) then
  200. write (*,*) 'XMLF - ERROR - ', trim(text), ' - at or near line', info%lineno
  201. else
  202. write (report_lun_,*) 'XMLF - ERROR - ', trim(text), ' - at or near line', info%lineno
  203. endif
  204. end subroutine xml_report_errors_extern_
  205. ! xml_open --
  206. ! Routine to open an XML file for reading or writing
  207. ! Arguments:
  208. ! info Structure holding information on the XML-file
  209. ! fname Name of the file
  210. ! mustread The file will be read (.true.) or written (.false.)
  211. !
  212. subroutine xml_open( info, fname, mustread )
  213. character(len=*), intent(in) :: fname
  214. logical, intent(in) :: mustread
  215. type(XML_PARSE), intent(out) :: info
  216. integer :: i
  217. integer :: k
  218. integer :: kend
  219. integer :: ierr
  220. logical :: opend
  221. logical :: exists
  222. info%lun = 10
  223. info%ignore_whitespace = .false.
  224. info%no_data_truncation = .false.
  225. info%too_many_attribs = .false.
  226. info%too_many_data = .false.
  227. info%eof = .false.
  228. info%error = .false.
  229. info%level = -1
  230. info%lineno = 0
  231. do i = 10,99
  232. inquire( unit = i, opened = opend )
  233. if ( .not. opend ) then
  234. info%lun = i
  235. inquire( file = fname, exist = exists )
  236. if ( .not. exists .and. mustread ) then
  237. call xml_report_errors( 'XML_OPEN: file does not exist:', trim(fname))
  238. info%lun = -1
  239. info%error = .true.
  240. else
  241. open( unit = info%lun, file = fname )
  242. call xml_report_details( 'XML_OPEN: opened file ', trim(fname) )
  243. call xml_report_details( 'at LU-number: ', info%lun )
  244. endif
  245. exit
  246. endif
  247. enddo
  248. if ( .not. info%error .and. mustread ) then
  249. k = 1
  250. do while ( k .ge. 1 )
  251. read( info%lun, '(a)', iostat = ierr ) info%line
  252. if ( ierr .eq. 0 ) then
  253. info%line = adjustl( info%line )
  254. k = index( info%line, '<?' )
  255. !
  256. ! Assume (for now at least) that <?xml ... ?> appears on a single line!
  257. !
  258. if ( k .ge. 1 ) then
  259. kend = index( info%line, '?>' )
  260. if ( kend .le. 0 ) then
  261. call xml_report_errors( 'XML_OPEN: error reading file with LU-number: ', info%lun )
  262. call xml_report_errors( 'Line starting with "<?xml" should end with "?>"', ' ' )
  263. info%error = .true.
  264. exit
  265. endif
  266. endif
  267. else
  268. call xml_report_errors( 'XML_OPEN: error reading file with LU-number: ', info%lun )
  269. call xml_report_errors( 'Possibly no line starting with "<?xml"', ' ' )
  270. call xml_close( info )
  271. info%error = .true.
  272. exit
  273. endif
  274. enddo
  275. endif
  276. if ( .not. info%error .and. .not. mustread ) then
  277. write( info%lun, '(a)' ) '<?xml version="1.0"?>'
  278. endif
  279. end subroutine xml_open
  280. ! xml_close --
  281. ! Routine to close an XML file
  282. ! Arguments:
  283. ! info Structure holding information on the XML-file
  284. !
  285. subroutine xml_close( info )
  286. type(XML_PARSE), intent(inout) :: info
  287. close( info%lun )
  288. !
  289. ! Only clean up the LU-number, so that the calling program
  290. ! can examine the last condition
  291. !
  292. call xml_report_details( 'XML_CLOSE: Closing file with LU-number ', info%lun )
  293. info%lun = -1
  294. end subroutine xml_close
  295. ! xml_get --
  296. ! Routine to get the next bit of information from an XML file
  297. ! Arguments:
  298. ! info Structure holding information on the XML-file
  299. ! tag Tag that was encountered
  300. ! endtag Whether the end of the element was encountered
  301. ! attribs List of attribute-value pairs
  302. ! no_attribs Number of pairs in the list
  303. ! data Lines of character data found
  304. ! no_data Number of lines of character data
  305. !
  306. subroutine xml_get( info, tag, endtag, attribs, no_attribs, &
  307. data, no_data )
  308. type(XML_PARSE), intent(inout) :: info
  309. character(len=*), intent(out) :: tag
  310. logical, intent(out) :: endtag
  311. character(len=*), intent(out), dimension(:,:) :: attribs
  312. integer, intent(out) :: no_attribs
  313. character(len=*), intent(out), dimension(:) :: data
  314. integer, intent(out) :: no_data
  315. integer :: kspace
  316. integer :: kend
  317. integer :: keq
  318. integer :: kfirst
  319. integer :: ksecond
  320. integer :: idxat
  321. integer :: idxdat
  322. integer :: ierr
  323. logical :: close_bracket
  324. logical :: comment_tag
  325. character(len=XML_BUFFER_LENGTH) :: nextline
  326. !
  327. ! Initialise the output
  328. !
  329. endtag = .false.
  330. no_attribs = 0
  331. no_data = 0
  332. info%too_many_attribs = .false.
  333. info%too_many_data = .false.
  334. if ( info%lun .lt. 0 ) then
  335. call xml_report_details( 'XML_GET on closed file ', ' ' )
  336. return
  337. endif
  338. !
  339. ! From the previous call or the call to xmlopen we have
  340. ! the line that we need to parse already in memory:
  341. ! <tag attrib1="..." attrib2="..." />
  342. !
  343. comment_tag = .false.
  344. close_bracket = .false.
  345. kspace = index( info%line, ' ' )
  346. kend = index( info%line, '>' )
  347. do while ( kend .le. 0 )
  348. read( info%lun, '(a)', iostat = ierr ) nextline
  349. info%lineno = info%lineno + 1
  350. if ( ierr .eq. 0 ) then
  351. info%line = trim(info%line) // ' ' // adjustl(nextline)
  352. else
  353. info%error = .true.
  354. call xml_report_errors( 'XML_GET - end of tag not found ', &
  355. '(buffer too small?)', info%lineno )
  356. call xml_close( info )
  357. return
  358. endif
  359. kend = index( info%line, '>' )
  360. enddo
  361. if ( kend .gt. kspace ) then
  362. kend = kspace
  363. else
  364. close_bracket = .true.
  365. endif
  366. !
  367. ! Check for the end of an ordianry tag and of
  368. ! a comment tag
  369. !
  370. if ( info%line(1:3) .eq. '-->' ) then
  371. endtag = .true.
  372. tag = info%line(4:kend-1)
  373. else if ( info%line(1:2) .eq. '</' ) then
  374. endtag = .true.
  375. tag = info%line(3:kend-1)
  376. else
  377. if ( info%line(1:1) .eq. '<' ) then
  378. tag = info%line(2:kend-1)
  379. call xml_report_details( 'XML_GET - tag found: ', trim(tag) )
  380. else
  381. kend = 0 ! Beginning of data!
  382. endif
  383. endif
  384. info%line = adjustl( info%line(kend+1:) )
  385. idxat = 0
  386. idxdat = 0
  387. if ( tag(1:3) .eq. '!--' ) comment_tag = .true.
  388. do while ( info%line .ne. ' ' .and. .not. close_bracket .and. .not. comment_tag )
  389. keq = index( info%line, '=' )
  390. kend = index( info%line, '>' )
  391. if ( keq .gt. kend ) keq = 0 ! Guard against multiple tags
  392. ! with attributes on one line
  393. !
  394. ! No attributes any more?
  395. !
  396. if ( keq .lt. 1 ) then
  397. kend = index( info%line, '/>' )
  398. if ( kend .ge. 1 ) then
  399. kend = kend + 1 ! To go beyond the ">" character
  400. endtag = .true.
  401. else
  402. kend = index( info%line, '>' )
  403. if ( kend .lt. 1 ) then
  404. call xml_report_errors( 'XML_GET - wrong ending of tag ', &
  405. trim(info%line), info%lineno )
  406. info%error = .true. ! Wrong ending of line!
  407. call xml_close( info )
  408. return
  409. else
  410. close_bracket = .true.
  411. endif
  412. endif
  413. if ( kend .ge. 1 ) then
  414. info%line = adjustl( info%line(kend+1:) )
  415. endif
  416. exit
  417. endif
  418. idxat = idxat + 1
  419. if ( idxat .le. size(attribs,2) ) then
  420. no_attribs = idxat
  421. attribs(1,idxat) = adjustl(info%line(1:keq-1)) ! Use adjustl() to avoid
  422. ! multiple spaces, etc
  423. info%line = adjustl( info%line(keq+1:) )
  424. !
  425. ! We have almost found the start of the attribute's value
  426. !
  427. kfirst = index( info%line, '"' )
  428. if ( kfirst .lt. 1 ) then
  429. call xml_report_errors( 'XML_GET - malformed attribute-value pair: ', &
  430. trim(info%line), info%lineno )
  431. info%error = .true. ! Wrong form of attribute-value pair
  432. call xml_close( info )
  433. return
  434. endif
  435. ksecond = index( info%line(kfirst+1:), '"' ) + kfirst
  436. if ( ksecond .lt. 1 ) then
  437. call xml_report_errors( 'XML_GET - malformed attribute-value pair: ', &
  438. trim(info%line), info%lineno )
  439. info%error = .true. ! Wrong form of attribute-value pair
  440. call xml_close( info )
  441. return
  442. endif
  443. attribs(2,idxat) = info%line(kfirst+1:ksecond-1)
  444. info%line = adjustl( info%line(ksecond+1:) )
  445. endif
  446. if ( idxat .gt. size(attribs,2) ) then
  447. call xml_report_errors( 'XML_GET - more attributes than could be stored: ', &
  448. trim(info%line), info%lineno )
  449. info%too_many_attribs = .true.
  450. info%line = ' '
  451. exit
  452. endif
  453. enddo
  454. !
  455. ! Now read the data associated with the current tag
  456. ! - all the way to the next "<" character
  457. !
  458. ! To do: reduce the number of data lines - empty ones
  459. ! at the end should not count.
  460. !
  461. do
  462. if ( comment_tag ) then
  463. kend = index( info%line, '-->' )
  464. else
  465. kend = index( info%line, '<' )
  466. endif
  467. idxdat = idxdat + 1
  468. if ( idxdat .le. size(data) ) then
  469. no_data = idxdat
  470. if ( kend .ge. 1 ) then
  471. data(idxdat) = info%line(1:kend-1)
  472. info%line = info%line(kend:)
  473. else
  474. data(idxdat) = info%line
  475. endif
  476. else
  477. call xml_report_errors( 'XML_GET - more data lines than could be stored: ', &
  478. trim(info%line), info%lineno )
  479. info%too_many_data = .true.
  480. exit
  481. endif
  482. !
  483. ! No more data? Otherwise, read on
  484. !
  485. if ( kend .ge. 1 ) then
  486. exit
  487. else
  488. read( info%lun, '(a)', iostat = ierr ) info%line
  489. info%lineno = info%lineno + 1
  490. if ( ierr .lt. 0 ) then
  491. call xml_report_details( 'XML_GET - end of file found - LU-number: ', &
  492. info%lun )
  493. info%eof = .true.
  494. elseif ( ierr .gt. 0 ) then
  495. call xml_report_errors( 'XML_GET - error reading file with LU-number ', &
  496. info%lun, info%lineno )
  497. info%error = .true.
  498. endif
  499. if ( ierr .ne. 0 ) then
  500. exit
  501. endif
  502. endif
  503. enddo
  504. !
  505. ! Compress the data?
  506. !
  507. if ( info%ignore_whitespace ) then
  508. call xml_compress_( data, no_data )
  509. endif
  510. !
  511. ! Replace the entities, if any
  512. !
  513. call xml_replace_entities_( data, no_data )
  514. call xml_report_details( 'XML_GET - number of attributes: ', no_attribs )
  515. call xml_report_details( 'XML_GET - number of data lines: ', no_data )
  516. end subroutine xml_get
  517. ! xml_put --
  518. ! Routine to write a tag with the associated data to an XML file
  519. ! Arguments:
  520. ! info Structure holding information on the XML-file
  521. ! tag Tag that was encountered
  522. ! endtag Whether the end of the element was encountered
  523. ! attribs List of attribute-value pairs
  524. ! no_attribs Number of pairs in the list
  525. ! data Lines of character data found
  526. ! no_data Number of lines of character data
  527. ! type Type of action:
  528. ! open - just the opening tag with attributes
  529. ! elem - complete element
  530. ! close - just the closing tag
  531. !
  532. subroutine xml_put(info, tag, attribs, no_attribs, &
  533. data, no_data, type)
  534. type(XML_PARSE), intent(inout) :: info
  535. character(len=*), intent(in) :: tag
  536. character(len=*), intent(in), dimension(:,:) :: attribs
  537. integer, intent(in) :: no_attribs
  538. character(len=*), intent(in), dimension(:) :: data
  539. integer, intent(in) :: no_data
  540. character(len=*) :: type
  541. integer :: i
  542. character(len=300), parameter :: indent = ' '
  543. select case(type)
  544. case('open')
  545. call xml_put_open_tag_(info, tag, attribs, no_attribs, &
  546. data, no_data)
  547. case('elem')
  548. call xml_put_element_(info, tag, attribs, no_attribs, &
  549. data, no_data)
  550. case('close')
  551. call xml_put_close_tag_(info, tag, attribs, no_attribs, &
  552. data, no_data)
  553. end select
  554. end subroutine xml_put
  555. ! xml_put_open_tag_ --
  556. ! Routine to write the opening tag with the attributes
  557. ! Arguments:
  558. ! info Structure holding information on the XML-file
  559. ! tag Tag that was encountered
  560. ! endtag Whether the end of the element was encountered
  561. ! attribs List of attribute-value pairs
  562. ! no_attribs Number of pairs in the list
  563. ! data Lines of character data found
  564. ! no_data Number of lines of character data
  565. !
  566. subroutine xml_put_open_tag_(info, tag, attribs, no_attribs, &
  567. data, no_data)
  568. type(XML_PARSE), intent(inout) :: info
  569. character(len=*), intent(in) :: tag
  570. character(len=*), intent(in), dimension(:,:) :: attribs
  571. integer, intent(in) :: no_attribs
  572. character(len=*), intent(in), dimension(:) :: data
  573. integer, intent(in) :: no_data
  574. character(len=1) :: aa
  575. integer :: i
  576. character(len=300), parameter :: indent = ' '
  577. write( info%lun, '(3a)', advance = 'no' ) &
  578. indent(1:3*info%level), '<', adjustl(tag)
  579. do i=1,no_attribs
  580. if (attribs(2,i).ne.'') then
  581. write( info%lun, '(5a)', advance = 'no' ) &
  582. ' ',trim(attribs(1,i)),'="', trim(attribs(2,i)),'"'
  583. endif
  584. enddo
  585. write( info%lun, '(a)' ) '>'
  586. info%level = info%level + 1
  587. end subroutine xml_put_open_tag_
  588. ! xml_put_element_ --
  589. ! Routine to write the complete element
  590. ! Arguments:
  591. ! info Structure holding information on the XML-file
  592. ! tag Tag that was encountered
  593. ! endtag Whether the end of the element was encountered
  594. ! attribs List of attribute-value pairs
  595. ! no_attribs Number of pairs in the list
  596. ! data Lines of character data found
  597. ! no_data Number of lines of character data
  598. !
  599. subroutine xml_put_element_(info, tag, attribs, no_attribs, &
  600. data, no_data)
  601. type(XML_PARSE), intent(inout) :: info
  602. character(len=*), intent(in) :: tag
  603. character(len=*), intent(in), dimension(:,:) :: attribs
  604. integer, intent(in) :: no_attribs
  605. character(len=*), intent(in), dimension(:) :: data
  606. integer, intent(in) :: no_data
  607. logical :: logic
  608. character(len=1) :: aa
  609. integer :: i, ii
  610. character(len=300), parameter :: indent = ' '
  611. if ( (no_attribs.eq.0 .and. no_data.eq.0) ) then
  612. return
  613. else
  614. logic = .true.
  615. do ii = 1,no_attribs
  616. logic = logic .and. (attribs(2,ii).eq.'')
  617. enddo
  618. do ii = 1,no_data
  619. logic = logic .and. (data(ii).eq.'')
  620. enddo
  621. if ( logic ) then
  622. return
  623. else
  624. write( info%lun, '(3a)', advance = 'no' ) &
  625. indent(1:3*info%level), '<', adjustl(tag)
  626. do i = 1,no_attribs
  627. if (attribs(2,i).ne.'') then
  628. write( info%lun, '(5a)', advance = 'no' ) &
  629. ' ',trim(attribs(1,i)),'="', trim(attribs(2,i)),'"'
  630. endif
  631. enddo
  632. if ( no_attribs.gt.0 .and. no_data.eq.0 ) then
  633. aa='a'
  634. elseif ( (no_attribs.gt.0 .and. no_data.gt.0) .or. &
  635. (no_attribs.eq.0 .and. no_data.gt.0) ) then
  636. aa='b'
  637. else
  638. write(*,*) no_attribs, no_data
  639. endif
  640. endif
  641. endif
  642. select case(aa)
  643. case('a')
  644. write( info%lun, '(a)' ) '/>'
  645. case('b')
  646. write( info%lun, '(a)',advance='no' ) '>'
  647. write( info%lun, '(2a)', advance='no') &
  648. ( ' ', trim(data(i)), i=1,no_data )
  649. write( info%lun, '(4a)' ) ' ','</', tag, '>'
  650. end select
  651. end subroutine xml_put_element_
  652. ! xml_put_close_tag_ --
  653. ! Routine to write the closing tag
  654. ! Arguments:
  655. ! info Structure holding information on the XML-file
  656. ! tag Tag that was encountered
  657. ! endtag Whether the end of the element was encountered
  658. ! attribs List of attribute-value pairs
  659. ! no_attribs Number of pairs in the list
  660. ! data Lines of character data found
  661. ! no_data Number of lines of character data
  662. !
  663. subroutine xml_put_close_tag_(info, tag, attribs, no_attribs, &
  664. data, no_data)
  665. type(XML_PARSE), intent(inout) :: info
  666. character(len=*), intent(in) :: tag
  667. character(len=*), intent(in), dimension(:,:) :: attribs
  668. integer, intent(in) :: no_attribs
  669. character(len=*), intent(in), dimension(:) :: data
  670. integer, intent(in) :: no_data
  671. integer :: i
  672. character(len=300), parameter :: indent = ' '
  673. info%level=info%level-1
  674. write( info%lun, '(4a)' ) &
  675. indent(1:3*info%level), '</', adjustl(tag), '>'
  676. end subroutine xml_put_close_tag_
  677. ! xml_compress_ --
  678. ! Routine to remove empty lines from the character data
  679. ! Arguments:
  680. ! data Lines of character data found
  681. ! no_data (Nett) number of lines of character data
  682. !
  683. subroutine xml_compress_( data, no_data )
  684. character(len=*), intent(inout), dimension(:) :: data
  685. integer, intent(inout) :: no_data
  686. integer :: i
  687. integer :: j
  688. logical :: empty
  689. j = 0
  690. empty = .true.
  691. do i = 1,no_data
  692. if ( len_trim(data(i)) .ne. 0 .or. .not. empty ) then
  693. j = j + 1
  694. data(j) = adjustl(data(i))
  695. empty = .false.
  696. endif
  697. enddo
  698. no_data = j
  699. do i = no_data,1,-1
  700. if ( len_trim(data(i)) .ne. 0 ) then
  701. exit
  702. else
  703. no_data = no_data - 1
  704. endif
  705. enddo
  706. end subroutine xml_compress_
  707. ! xml_replace_entities_ --
  708. ! Routine to replace entities such as &gt; by their
  709. ! proper character representation
  710. ! Arguments:
  711. ! data Lines of character data found
  712. ! no_data (Nett) number of lines of character data
  713. !
  714. subroutine xml_replace_entities_( data, no_data )
  715. character(len=*), intent(inout), dimension(:) :: data
  716. integer, intent(inout) :: no_data
  717. integer :: i
  718. integer :: j
  719. integer :: j2
  720. integer :: k
  721. integer :: pos
  722. logical :: found
  723. do i = 1,no_data
  724. j = 1
  725. do
  726. do k = 1,size(entities,2)
  727. found = .false.
  728. pos = index( data(i)(j:), trim(entities(2,k)) )
  729. if ( pos .gt. 0 ) then
  730. found = .true.
  731. j = j + pos - 1
  732. j2 = j + len_trim(entities(2,k))
  733. data(i)(j:) = trim(entities(1,k)) // data(i)(j2:)
  734. j = j2
  735. endif
  736. enddo
  737. if ( .not. found ) exit
  738. enddo
  739. enddo
  740. end subroutine xml_replace_entities_
  741. ! xml_options --
  742. ! Routine to handle the parser options
  743. ! Arguments:
  744. ! info Structure holding information on the XML-file
  745. ! ignore_whitespace Ignore whitespace (leading blanks, empty lines) or not
  746. ! no_data_truncation Consider truncation of strings an error or not
  747. ! report_lun LU-number for reporting information
  748. ! report_errors Write messages about errors or not
  749. ! report_details Write messages about all kinds of actions or not
  750. !
  751. subroutine xml_options( info, ignore_whitespace, no_data_truncation, &
  752. report_lun, report_errors, &
  753. report_details )
  754. type(XML_PARSE), intent(inout) :: info
  755. logical, intent(in), optional :: ignore_whitespace
  756. logical, intent(in), optional :: no_data_truncation
  757. integer, intent(in), optional :: report_lun
  758. logical, intent(in), optional :: report_errors
  759. logical, intent(in), optional :: report_details
  760. if ( present(ignore_whitespace) ) then
  761. info%ignore_whitespace = ignore_whitespace
  762. endif
  763. if ( present(no_data_truncation) ) then
  764. info%no_data_truncation = no_data_truncation
  765. endif
  766. if ( present(report_lun) ) then
  767. report_lun_ = report_lun
  768. endif
  769. if ( present(report_errors) ) then
  770. report_errors_ = report_errors
  771. endif
  772. if ( present(report_details) ) then
  773. report_details_ = report_details
  774. endif
  775. end subroutine xml_options
  776. ! xml_ok --
  777. ! Function that returns whether all was okay or not
  778. ! Arguments:
  779. ! info Structure holding information on the XML-file
  780. ! Returns:
  781. ! .true. if there was no error, .false. otherwise
  782. !
  783. logical function xml_ok( info )
  784. type(XML_PARSE), intent(in) :: info
  785. xml_ok = info%eof .or. info%error .or. &
  786. ( info%no_data_truncation .and. &
  787. ( info%too_many_attribs .or. info%too_many_data ) )
  788. xml_ok = .not. xml_ok
  789. end function xml_ok
  790. ! xml_error --
  791. ! Function that returns whether there was an error
  792. ! Arguments:
  793. ! info Structure holding information on the XML-file
  794. ! Returns:
  795. ! .true. if there was an error, .false. if there was none
  796. !
  797. logical function xml_error( info )
  798. type(XML_PARSE), intent(in) :: info
  799. xml_error = info%error .or. &
  800. ( info%no_data_truncation .and. &
  801. ( info%too_many_attribs .or. info%too_many_data ) )
  802. end function xml_error
  803. ! xml_data_trunc --
  804. ! Function that returns whether data were truncated or not
  805. ! Arguments:
  806. ! info Structure holding information on the XML-file
  807. ! Returns:
  808. ! .true. if data were truncated, .false. otherwise
  809. !
  810. logical function xml_data_trunc( info )
  811. type(XML_PARSE), intent(in) :: info
  812. xml_data_trunc = info%too_many_attribs .or. info%too_many_data
  813. end function xml_data_trunc
  814. integer function xml_find_attrib( attribs, no_attribs, name, value )
  815. character(len=*), dimension(:,:) :: attribs
  816. integer :: no_attribs
  817. character(len=*) :: name
  818. character(len=*) :: value
  819. integer :: i
  820. xml_find_attrib = -1
  821. do i = 1,no_attribs
  822. if ( name .eq. attribs(1,i) ) then
  823. value = attribs(2,i)
  824. xml_find_attrib = i
  825. exit
  826. endif
  827. enddo
  828. end function xml_find_attrib
  829. ! xml_process --
  830. ! Routine to read the XML file as a whole and distribute processing
  831. ! the contents over three user-defined subroutines
  832. ! Arguments:
  833. ! filename Name of the file to process
  834. ! attribs Array for holding the attributes
  835. ! data Array for holding the character data
  836. ! startfunc Subroutine to handle the start of elements
  837. ! datafunc Subroutine to handle the character data
  838. ! endfunc Subroutine to handle the end of elements
  839. ! error Indicates if there was an error or not
  840. ! Note:
  841. ! The routine is declared recursive to allow inclusion of XML files
  842. ! (common with XSD schemas). This extends to the auxiliary routines.
  843. !
  844. recursive &
  845. subroutine xml_process( filename, attribs, data, startfunc, datafunc, endfunc, lunrep, error )
  846. character(len=*) :: filename
  847. character(len=*), dimension(:,:) :: attribs
  848. character(len=*), dimension(:) :: data
  849. integer :: lunrep
  850. logical :: error
  851. interface
  852. recursive subroutine startfunc( tag, attribs, error )
  853. character(len=*) :: tag
  854. character(len=*), dimension(:,:) :: attribs
  855. logical :: error
  856. end subroutine
  857. end interface
  858. interface
  859. recursive subroutine datafunc( tag, data, error )
  860. character(len=*) :: tag
  861. character(len=*), dimension(:) :: data
  862. logical :: error
  863. end subroutine
  864. end interface
  865. interface
  866. recursive subroutine endfunc( tag, error )
  867. character(len=*) :: tag
  868. logical :: error
  869. end subroutine
  870. end interface
  871. type(XML_PARSE) :: info
  872. character(len=80) :: tag
  873. logical :: endtag
  874. integer :: noattribs
  875. integer :: nodata
  876. call xml_options( info, report_lun = lunrep, report_details = .false. )
  877. call xml_open( info, filename, .true. )
  878. error = .false.
  879. do
  880. call xml_get( info, tag, endtag, attribs, noattribs, data, nodata )
  881. if ( .not. xml_ok(info) ) then
  882. exit
  883. endif
  884. if ( xml_error(info) ) then
  885. write(lunrep,*) 'Error reading XML file!'
  886. error = .true.
  887. exit
  888. endif
  889. if ( .not. endtag .or. noattribs .ne. 0 ) then
  890. call startfunc( tag, attribs(:,1:noattribs), error )
  891. if ( error ) exit
  892. call datafunc( tag, data(1:nodata), error )
  893. if ( error ) exit
  894. endif
  895. if ( endtag ) then
  896. call endfunc( tag, error )
  897. if ( error ) exit
  898. endif
  899. enddo
  900. call xml_close( info )
  901. end subroutine xml_process
  902. end module xmlf_parse