class XML_ROUTINES_IMP
XML routines
note
description: "XML routines"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2023-12-10 16:42:56 GMT (Sunday 10th December 2023)"
revision: "29"
class
XML_ROUTINES_IMP
inherit
EL_MARKUP_ROUTINES
XML_ZSTRING_CONSTANTS
export
{NONE} all
end
EL_MODULE_FILE_SYSTEM
EL_SHARED_FORMAT_FACTORY
feature -- Measurement
data_payload_character_count (xml_text: ZSTRING): INTEGER
-- approximate count of text between tags
local
data_from, data_to, i: INTEGER
has_data: BOOLEAN
do
if attached xml_text.substring_intervals (Bracket.left_slash, False) as list then
from list.start until list.after loop
data_to := list.item_lower - 1
data_from := xml_text.last_index_of ('>', data_to) + 1
has_data := False
from i := data_from until has_data or i > data_to loop
has_data := not xml_text.is_space_item (i)
i := i + 1
end
if has_data then
Result := Result + data_to - (i - 1) + 1
end
list.forth
end
end
end
feature -- Access
document_text (doc_xpath, encoding_name, text: STRING): STRING
-- create XML document with text enclosed by nested elements specified by `doc_xpath'
-- Eg. "body", "html/body"
local
lines, enclosing_elements: EL_STRING_8_LIST
do
create enclosing_elements.make_split (doc_xpath, '/')
create lines.make (3 + enclosing_elements.count * 2)
lines.extend (header (1.0, encoding_name))
across enclosing_elements as element loop
lines.extend (open_tag (element.item))
end
lines.extend (text)
across enclosing_elements.new_cursor.reversed as element loop
lines.extend (closed_tag (element.item))
end
Result := lines.joined_lines
end
encoding (file_path: FILE_PATH): EL_MARKUP_ENCODING
do
create Result.make_from_file (file_path)
end
entity (unicode: CHARACTER_32): ZSTRING
do
Result := xml_escaper.escape_sequence (unicode)
end
header (a_version: REAL; encoding_name: STRING): ZSTRING
do
Result := Header_template #$ [Format.double_as_string (a_version, once "9.9"), encoding_name]
ensure
no_leading_tab: not Result.starts_with_character ('%T')
end
root_element_name (a_xml: READABLE_STRING_GENERAL): STRING
--
local
left_bracket_index, i: INTEGER
do
left_bracket_index := a_xml.last_index_of ('<', a_xml.count)
if left_bracket_index > 0 then
create Result.make (a_xml.count - left_bracket_index - 3)
i := left_bracket_index + 1
if a_xml [i] = '/' then
i := i + 1
end
from until i > a_xml.count or else not is_identifier (a_xml [i].to_character_8) loop
Result.append_character (a_xml [i].to_character_8)
i := i + 1
end
else
create Result.make_empty
end
end
feature -- Document status
is_xml_declaration (text: READABLE_STRING_8): BOOLEAN
do
Result := text.count >= 5 and then text [2] = '?' and then text.same_caseless_characters (Xml, 1, 3, 3)
end
is_namespace_aware (a_xml: READABLE_STRING_8): BOOLEAN
-- `True' if xmlns name exists in document root element
local
tag_splitter: EL_SPLIT_ON_CHARACTER_8 [READABLE_STRING_8]
done: BOOLEAN
do
create tag_splitter.make (a_xml, '%N')
across tag_splitter as split until done or Result loop
if attached split.item as line then
if has_xmlns_attribute (line) then
Result := True
elseif has_element_ending (line) then
done := True
end
end
end
end
is_namespace_aware_file (path: FILE_PATH): BOOLEAN
-- `True' if xmlns name exists in document root element
require
path_exists: path.exists
local
file: PLAIN_TEXT_FILE; done: BOOLEAN
do
create file.make_with_name (path)
if file.exists then
from file.open_read until Result or done loop
file.read_line
if file.end_of_file then
done := True
elseif attached file.last_string as line then
if has_xmlns_attribute (line) then
Result := True
elseif has_element_ending (line) then
done := True
end
end
end
file.close
end
end
feature -- Conversion
escaped (a_string: ZSTRING): ZSTRING
-- Escapes characters: < > & '
do
Result := a_string.escaped (Xml_escaper)
end
escaped_128_plus (a_string: ZSTRING): ZSTRING
-- Escapes characters: < > & ' and all codes > 128
do
Result := a_string.escaped (Xml_128_plus_escaper)
end
feature -- Basic operations
append_attribute (name, value, element: ZSTRING)
-- append `name' and `value' to element string as XML attribute
do
if value.count > 0 then
element.append_character (' ')
element.append (name)
element.append_character ('=')
element.append_character ('"')
element.append (value)
element.append_character ('"')
end
end
feature -- Constants
Default_doc: EL_DEFAULT_SERIALIZEABLE_XML
-- <?xml version="1.0" encoding="UTF-8"?>
-- <default/>
once
create Result
end
Non_breaking_space: STRING = " "
XMLNS: STRING = "xmlns"
Xml: STRING = "xml"
feature {NONE} -- Implementation
code (char: CHARACTER): NATURAL
do
Result := char.natural_32_code
end
is_identifier (c: CHARACTER): BOOLEAN
do
inspect c
when 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_', '-' then
Result := True
else
end
end
has_element_ending (str: READABLE_STRING_8): BOOLEAN
-- `True' if `str' has right angle bracket and is not a comment
-- or processing instruction
local
rbracket_index: INTEGER; c: CHARACTER
do
rbracket_index := str.index_of ('>', 1)
if rbracket_index > 1 then
c := str [rbracket_index - 1]
inspect c
when 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '/', '"' then
Result := True
else
Result := c.is_space
end
end
end
has_xmlns_attribute (str: READABLE_STRING_8): BOOLEAN
local
index_xml_ns: INTEGER
do
index_xml_ns := str.substring_index (XMLNS, 1)
if index_xml_ns > 0 and then str.valid_index (index_xml_ns + XMLNS.count) then
inspect str [index_xml_ns + XMLNS.count]
when ' ', '=', ':' then
Result := True
else
end
end
end
end