UNPKG

s2-tools

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

263 lines 9.13 kB
/** * Count the number of times a substring appears in a string * @param string - the string * @param substring - the substring * @returns the number of times the substring appears in the string */ export function xmlCountSubstring(string, substring) { const pattern = new RegExp(substring, 'g'); const match = string.match(pattern); return match !== null ? match.length : 0; } /** * Find the first tag with the given name * @param xml - the xml string * @param tagName - the tag name * @param options - user defined options * @returns the first tag with the given name */ export function xmlFindTagByName(xml, tagName, options) { const debug = options?.debug ?? false; const nested = !(options?.nested === false); const startIndex = options?.startIndex ?? 0; if (debug) console.info('[xml-utils] starting findTagByName with', tagName, ' and ', options); const start = xmlIndexOfMatch(xml, `<${tagName}[ \n>/]`, startIndex); if (debug) console.info('[xml-utils] start:', start); if (start === -1) return undefined; const afterStart = xml.slice(start + tagName.length); let relativeEnd = xmlIndexOfMatchEnd(afterStart, '^[^<]*[ /]>', 0); const selfClosing = relativeEnd !== -1 && afterStart[relativeEnd - 1] === '/'; if (debug) console.info('[xml-utils] selfClosing:', selfClosing); if (selfClosing === false) { // check if tag has subtags with the same name if (nested) { let startIndex = 0; let openings = 1; let closings = 0; while ((relativeEnd = xmlIndexOfMatchEnd(afterStart, '[ /]' + tagName + '>', startIndex)) !== -1) { const clip = afterStart.substring(startIndex, relativeEnd + 1); openings += xmlCountSubstring(clip, '<' + tagName + '[ \n\t>]'); closings += xmlCountSubstring(clip, '</' + tagName + '>'); // we can't have more openings than closings if (closings >= openings) break; startIndex = relativeEnd; } } else { relativeEnd = xmlIndexOfMatchEnd(afterStart, '[ /]' + tagName + '>', 0); } } const end = start + tagName.length + relativeEnd + 1; if (debug) console.info('[xml-utils] end:', end); if (end === -1) return undefined; const outer = xml.slice(start, end); // tag is like <gml:identifier codeSpace="OGP">urn:ogc:def:crs:EPSG::32617</gml:identifier> let inner; if (selfClosing) { inner = null; } else { inner = outer.slice(outer.indexOf('>') + 1, outer.lastIndexOf('<')); } return { inner, outer, start, end }; } /** * Find the first tag with the given path * @param xml - the xml string * @param path - the path * @param options - user defined options * @returns the first tag with the given path */ export function xmlFindTagByPath(xml, path, options) { const debug = options?.debug ?? false; const found = xmlFindTagsByPath(xml, path, { debug, returnOnFirst: true }); if (Array.isArray(found) && found.length === 1) return found[0]; else return undefined; } /** * Find all tags with the given name * @param xml - the xml string * @param tagName - the tag name * @param options - user defined options * @returns all tags with the given name */ export function xmlFindTagsByName(xml, tagName, options) { const tags = []; const debug = options?.debug ?? false; const nested = options?.nested ?? true; let startIndex = options?.startIndex ?? 0; while (true) { const tag = xmlFindTagByName(xml, tagName, { debug, startIndex }); if (tag === undefined) break; if (nested) { startIndex = tag.start + 1 + tagName.length; } else { startIndex = tag.end; } tags.push(tag); } if (debug) console.info('findTagsByName found', tags.length, 'tags'); return tags; } /** * Find all tags with the given path * @param xml - the xml string * @param path - the path * @param options - user defined options * @returns all tags with the given path */ export function xmlFindTagsByPath(xml, path, options) { const debug = options?.debug ?? false; if (debug) console.info('[xml-utils] starting findTagsByPath with: ', xml.substring(0, 500)); const returnOnFirst = options?.returnOnFirst ?? false; if (Array.isArray(path) === false) throw new Error('[xml-utils] path should be an array'); const path0 = typeof path[0] === 'string' ? { name: path[0] } : path[0]; let tags = xmlFindTagsByName(xml, path0.name, { debug, nested: false }); if (typeof tags !== 'undefined' && typeof path0.index === 'number') { if (typeof tags[path0.index] === 'undefined') { tags = []; } else { tags = [tags[path0.index]]; } } if (debug) console.info('first tags are:', tags); path = path.slice(1); for (let pathIndex = 0; pathIndex < path.length; pathIndex++) { const part = typeof path[pathIndex] === 'string' ? { name: path[pathIndex] } : path[pathIndex]; if (debug) console.info('part.name:', part.name); let allSubTags = []; for (let tagIndex = 0; tagIndex < tags.length; tagIndex++) { const tag = tags[tagIndex]; const subTags = xmlFindTagsByName(tag.outer, part.name, { debug, startIndex: 1, }); if (debug) console.info('subTags.length:', subTags.length); if (subTags.length > 0) { subTags.forEach((subTag) => { subTag.start += tag.start; subTag.end += tag.start; }); if (returnOnFirst && pathIndex === path.length - 1) return [subTags[0]]; allSubTags = allSubTags.concat(subTags); } } tags = allSubTags; if ('index' in part && typeof part.index === 'number') { if (typeof tags[part.index] === 'undefined') { tags = []; } else { tags = [tags[part.index]]; } } } return tags; } /** * Get the value of an attribute * @param tag - the tag * @param attributeName - the attribute name * @param options - user defined options * @returns the attribute value */ export function xmlGetAttribute(tag, attributeName, options) { const debug = options?.debug ?? false; if (debug) console.info('[xml-utils] getting ' + attributeName + ' in ' + tag); const xml = typeof tag === 'object' ? tag.outer : tag; // only search for attributes in the opening tag const opening = xml.slice(0, xml.indexOf('>') + 1); const quotechars = ['"', "'"]; for (let i = 0; i < quotechars.length; i++) { const char = quotechars[i]; const pattern = attributeName + '\\=' + char + '([^' + char + ']*)' + char; if (debug) console.info('[xml-utils] pattern:', pattern); const re = new RegExp(pattern); const match = re.exec(opening); if (debug) console.info('[xml-utils] match:', match); if (match !== null) return match[1]; } } /** * Find the index of the last match * @param xml - the xml string * @param pattern - the pattern * @param startIndex - the start index * @returns the index of the last match */ export function xmlIndexOfMatchEnd(xml, pattern, startIndex) { const re = new RegExp(pattern); const match = re.exec(xml.slice(startIndex)); if (match !== null) return startIndex + match.index + match[0].length - 1; else return -1; } /** * Find the index of the first match * @param xml - the xml string * @param pattern - the pattern * @param startIndex - the start index * @returns the index of the first match */ export function xmlIndexOfMatch(xml, pattern, startIndex) { const re = new RegExp(pattern); const match = re.exec(xml.slice(startIndex)); if (match !== null) return startIndex + match.index; else return -1; } /** * Remove comments * @param xml - the xml string * @returns the xml without comments */ export function xmlRemoveComments(xml) { return xml.replace(/<!--[^]*-->/g, ''); } /** * Remove tags * @param xml - the xml string * @param tagName - the tag name * @param options - user defined options * @returns the xml without the given tag */ export function xmlRemoveTagsByName(xml, tagName, options) { const debug = options?.debug ?? false; while (true) { const tag = xmlFindTagByName(xml, tagName, { debug }); if (tag === undefined) break; xml = xml.substring(0, tag.start) + xml.substring(tag.end); if (debug) console.info('[xml-utils] removed:', tag); } return xml; } //# sourceMappingURL=parsing.js.map