UNPKG

@syncfusion/ej2-pdf

Version:

Feature-rich JavaScript PDF library with built-in support for loading and manipulating PDF document.

621 lines (620 loc) 22.2 kB
import { PdfXmpMetadata } from './pdf-xmp-metadata'; import { PdfCustomSchema } from './pdf-custom-schema'; import { _bytesToString } from '../utils'; /** * Internal XML reader for parsing XMP metadata from PDF streams. * Reads RDF/XML format and reconstructs PdfXmpMetadata with all schemas. * * @private */ var _XmlReader = /** @class */ (function () { function _XmlReader() { } /** * Loads and parses XML data from a byte array or string. * * @private * @param {Uint8Array | string} data The XML data to parse. * @returns {void} */ _XmlReader.prototype._load = function (data) { var pdfString; if (data instanceof Uint8Array) { pdfString = _bytesToString(data); } else { pdfString = data; } var start = pdfString.lastIndexOf('<?xpacket begin'); if (start === -1) { throw new Error('XMP metadata not found in PDF'); } var end = pdfString.indexOf('<?xpacket end=', start); if (end === -1) { throw new Error('XMP metadata not found in PDF'); } var xmlString = pdfString.substring(start, pdfString.indexOf('?>', end) + 2); var parser = new DOMParser(); this._xmlDoc = parser.parseFromString(xmlString, 'application/xml'); this._validate(this._xmlDoc); }; /** * Validates the parsed XML document for errors. * * @private * @param {Document} doc The XML document to validate. * @returns {void} */ _XmlReader.prototype._validate = function (doc) { var parserError = doc.querySelector('parsererror'); if (parserError) { var errorText = parserError.textContent || 'Unknown parse error'; throw new Error('Invalid XMP XML: ' + errorText); } }; /** * Parses the loaded XML document into a PdfXmpMetadata object. * * @private * @returns {PdfXmpMetadata} The parsed metadata object. */ _XmlReader.prototype._parseXmp = function () { this._xmp = new PdfXmpMetadata(); var rdfRoot = this._getRdfRoot(); var descriptions = rdfRoot.getElementsByTagNameNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'Description'); for (var i = 0; i < descriptions.length; i++) { this._parseSchemaNode(descriptions.item(i)); } return this._xmp; }; /** * Locates the rdf:RDF root element in the XML document. * * @private * @returns {Element} The rdf:RDF element. */ _XmlReader.prototype._getRdfRoot = function () { if (!this._xmlDoc) { throw new Error('XML document not loaded'); } var rdfElements = this._xmlDoc.getElementsByTagNameNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'RDF'); if (rdfElements.length === 0) { throw new Error('rdf:RDF element not found in XMP metadata'); } return rdfElements[0]; }; /** * Parses a single rdf:Description node and routes to appropriate schema parser. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseSchemaNode = function (node) { var attributes = node.attributes; for (var i = 0; i < attributes.length; i++) { var attr = attributes.item(i); if (attr.name.startsWith('xmlns:')) { var prefix = attr.name.substring(6); var namespaceUri = attr.value; this._mapSchema(prefix, namespaceUri, node); } } }; /** * Routes schema parsing based on namespace URI. * * @private * @param {string} prefix The namespace prefix. * @param {string} ns The namespace URI. * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._mapSchema = function (prefix, ns, node) { switch (ns) { case 'http://purl.org/dc/elements/1.1/': this._parseDublinCore(node); break; case 'http://ns.adobe.com/xap/1.0/': this._parseBasic(node); break; case 'http://ns.adobe.com/pdf/1.3/': this._parsePdf(node); break; case 'http://ns.adobe.com/xap/1.0/t/pg/': this._parsePagedText(node); break; case 'http://ns.adobe.com/xap/1.0/rights/': this._parseRights(node); break; case 'http://ns.adobe.com/xap/1.0/bj/': this._parseJobTicket(node); break; case 'http://ns.adobe.com/pdfx/1.3/': this._parseInternalCustom(prefix, ns, node); break; default: this._parseCustom(prefix, ns, node); break; } }; /** * Parses Basic XMP schema properties. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseBasic = function (node) { var schema = this._xmp.basicSchema; //eslint-disable-line var creatorTool = this._getValue(node, 'CreatorTool'); if (creatorTool) { schema.creatorTool = creatorTool; } var label = this._getValue(node, 'Label'); if (label) { schema.label = label; } var nickname = this._getValue(node, 'Nickname'); if (nickname) { schema.nickname = nickname; } var baseUrl = this._getValue(node, 'BaseURL'); if (baseUrl) { schema.baseUrl = baseUrl; } var createDate = this._getDate(node, 'CreateDate'); if (createDate) { schema.createDate = createDate; } var modifyDate = this._getDate(node, 'ModifyDate'); if (modifyDate) { schema.modifyDate = modifyDate; } var metadataDate = this._getDate(node, 'MetadataDate'); if (metadataDate) { schema.metadataDate = metadataDate; } var advisory = this._getArray(node, 'Advisory'); if (advisory.length > 0) { schema.advisory = advisory; } var identifier = this._getArray(node, 'Identifier'); if (identifier.length > 0) { schema.identifier = identifier; } var thumbnails = this._getThumbnails(node, 'Thumbnails'); if (thumbnails.length > 0) { schema.thumbnails = thumbnails; } var rating = this._getArray(node, 'Rating'); if (rating.length > 0) { schema.rating = rating.map(function (r) { return parseFloat(r); }); } }; /** * Parses Dublin Core schema properties. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseDublinCore = function (node) { var schema = this._xmp.dublinCoreSchema; var contributor = this._getArray(node, 'contributor'); if (contributor.length > 0) { schema.contributor = contributor; } var creator = this._getArray(node, 'creator'); if (creator.length > 0) { schema.creator = creator; } var date = this._getArray(node, 'date'); if (date.length > 0) { schema.date = date; } var publisher = this._getArray(node, 'publisher'); if (publisher.length > 0) { schema.publisher = publisher; } var relation = this._getArray(node, 'relation'); if (relation.length > 0) { schema.relation = relation; } var subject = this._getArray(node, 'subject'); if (subject.length > 0) { schema.subject = subject; } var type = this._getArray(node, 'type'); if (type.length > 0) { schema.type = type; } var title = this._getLangArray(node, 'title'); if (Object.keys(title).length > 0) { schema.title = title; } var description = this._getLangArray(node, 'description'); if (Object.keys(description).length > 0) { schema.description = description; } var rights = this._getLangArray(node, 'rights'); if (Object.keys(rights).length > 0) { schema.rights = rights; } var coverage = this._getValue(node, 'coverage'); if (coverage) { schema.coverage = coverage; } var identifier = this._getValue(node, 'identifier'); if (identifier) { schema.identifier = identifier; } var source = this._getValue(node, 'source'); if (source) { schema.source = source; } var format = this._getValue(node, 'format'); if (format) { schema.format = format; } }; /** * Parses PDF schema properties. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parsePdf = function (node) { var schema = this._xmp.pdfSchema; var keywords = this._getValue(node, 'Keywords'); if (keywords) { schema.keywords = keywords; } var producer = this._getValue(node, 'Producer'); if (producer) { schema.producer = producer; } var pdfVersion = this._getValue(node, 'PDFVersion'); if (pdfVersion) { schema.pdfVersion = pdfVersion; } }; /** * Parses Paged-Text schema properties. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parsePagedText = function (node) { var schema = this._xmp.pagedTextSchema; var pageCount = this._getValue(node, 'NPages'); if (pageCount) { schema.pageCount = parseInt(pageCount, 10); } var maxPageSizeElement = this._findChildElement(node, 'MaxPageSize'); if (maxPageSizeElement) { var descriptionElement = this._findDirectChild(maxPageSizeElement, 'Description'); if (descriptionElement) { var w = this._getValue(descriptionElement, 'w'); var h = this._getValue(descriptionElement, 'h'); var unit = this._getValue(descriptionElement, 'unit'); if (w && h) { var dimensions = { width: parseFloat(w), height: parseFloat(h) }; if (unit) { dimensions.unit = unit; } schema.maxPageSize = dimensions; } } } var fonts = this._getArray(node, 'Fonts'); if (fonts.length > 0) { schema.fonts = fonts; } var plateNames = this._getArray(node, 'PlateNames'); if (plateNames.length > 0) { schema.plateNames = plateNames; } var colorants = this._getArray(node, 'Colorants'); if (colorants.length > 0) { schema.colorants = colorants; } }; /** * Parses Rights Management schema properties. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseRights = function (node) { var schema = this._xmp.rightsManagementSchema; var certificateUrl = this._getValue(node, 'Certificate'); if (certificateUrl) { schema.certificateUrl = certificateUrl; } var webStatement = this._getValue(node, 'WebStatement'); if (webStatement) { schema.webStatement = webStatement; } var marked = this._getValue(node, 'Marked'); if (marked) { schema.isMarked = marked === 'True'; } var owners = this._getArray(node, 'Owner'); if (owners.length > 0) { schema.owners = owners; } var usageTerms = this._getLangArray(node, 'UsageTerms'); if (Object.keys(usageTerms).length > 0) { schema._setProperty('xmpRights:UsageTerms', usageTerms); //eslint-disable-line } }; /** * Parses Basic Job Ticket schema properties. * * @private * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseJobTicket = function (node) { var schema = this._xmp.basicJobTicketSchema; var jobRef = this._getArray(node, 'JobRef'); if (jobRef.length > 0) { schema.jobRef = jobRef; } }; /** * Parses custom schema properties. * * @private * @param {string} prefix The namespace prefix. * @param {string} ns The namespace URI. * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseInternalCustom = function (prefix, ns, node) { var customSchema = new PdfCustomSchema(this._xmp, prefix, ns); var children = node.querySelectorAll('*'); for (var i = 0; i < children.length; i++) { var child = children.item(i); if (child.prefix === prefix || child.localName.startsWith(prefix + ':')) { var key = child.localName.replace(prefix + ':', ''); var value = child.textContent || ''; customSchema.customData.set(key, value); } } if (customSchema.customData.size > 0) { this._xmp._customSchema = customSchema; } }; /** * Parses custom schema properties. * * @private * @param {string} prefix The namespace prefix. * @param {string} ns The namespace URI. * @param {Element} node The rdf:Description element. * @returns {void} */ _XmlReader.prototype._parseCustom = function (prefix, ns, node) { var customSchema = new PdfCustomSchema(this._xmp, prefix, ns); var children = node.querySelectorAll('*'); for (var i = 0; i < children.length; i++) { var child = children.item(i); if (child.prefix === prefix || child.localName.startsWith(prefix + ':')) { var key = child.localName.replace(prefix + ':', ''); var value = child.textContent || ''; customSchema.customData.set(key, value); } } }; /** * Gets a single text value from a child element. * * @private * @param {Element} node The parent element. * @param {string} tag The local name of the child element. * @returns {string} The text content or undefined. */ _XmlReader.prototype._getValue = function (node, tag) { var child = this._findChildElement(node, tag); if (child) { var textContent = child.textContent ? child.textContent.trim() : ''; if (textContent) { return textContent; } } var attr = node.getAttribute(this._getAttributeName(node, tag)); if (attr) { return attr; } return undefined; }; /** * Gets the full attribute name for a tag, checking all possible namespace prefixes. * * @private * @param {Element} node The element to check. * @param {string} localName The local name to search for. * @returns {string} The attribute name or null. */ _XmlReader.prototype._getAttributeName = function (node, localName) { var attributes = node.attributes; for (var i = 0; i < attributes.length; i++) { var attr = attributes.item(i); if (attr.localName === localName) { return attr.name; } } return null; }; /** * Gets a Date value from a child element. * * @private * @param {Element} node The parent element. * @param {string} tag The local name of the child element. * @returns {Date} The parsed date or undefined. */ _XmlReader.prototype._getDate = function (node, tag) { var value = this._getValue(node, tag); if (value) { var date = new Date(value); if (!isNaN(date.getTime())) { return date; } } return undefined; }; /** * Gets an array of string values from an rdf:Bag or rdf:Seq container. * * @private * @param {Element} node The parent element. * @param {string} tag The local name of the property element. * @returns {string[]} Array of string values. */ _XmlReader.prototype._getArray = function (node, tag) { var result = []; var child = this._findChildElement(node, tag); if (child) { var bag = this._findDirectChild(child, 'Bag') || this._findDirectChild(child, 'Seq'); if (bag) { var items = this._findDirectChildren(bag, 'li'); for (var _i = 0, items_1 = items; _i < items_1.length; _i++) { var item = items_1[_i]; var text = item.textContent; if (text) { result.push(text.trim()); } } } } return result; }; /** * Gets a multilingual array (language map) from an rdf:Alt container. * * @private * @param {Element} node The parent element. * @param {string} tag The local name of the property element. * @returns {PdfXmpLangArray} Language map object. */ _XmlReader.prototype._getLangArray = function (node, tag) { var result = {}; var child = this._findChildElement(node, tag); if (child) { var alt = this._findDirectChild(child, 'Alt'); if (alt) { var items = this._findDirectChildren(alt, 'li'); for (var _i = 0, items_2 = items; _i < items_2.length; _i++) { var item = items_2[_i]; var lang = item.getAttribute('xml:lang'); var text = item.textContent; if (lang && text && lang !== '__proto__' && lang !== 'constructor' && lang !== 'prototype') { result[String(lang)] = text.trim(); } } } } return result; }; /** * Gets an array of structured thumbnail objects from an rdf:Bag container. * * @private * @param {Element} node The parent element. * @param {string} tag The local name of the property element. * @returns {PdfXmpThumbnail[]} Array of thumbnail structures. */ _XmlReader.prototype._getThumbnails = function (node, tag) { var result = []; var child = this._findChildElement(node, tag); if (child) { var bag = this._findDirectChild(child, 'Bag'); if (bag) { var items = this._findDirectChildren(bag, 'li'); for (var _i = 0, items_3 = items; _i < items_3.length; _i++) { var item = items_3[_i]; var width = this._getValue(item, 'width') || this._getValue(item, 'Width'); var height = this._getValue(item, 'height') || this._getValue(item, 'Height'); var format = this._getValue(item, 'format') || this._getValue(item, 'Format'); var image = this._getValue(item, 'image') || this._getValue(item, 'Image'); if (width && height && format && image) { var thumbnail = { width: parseInt(width, 10), height: parseInt(height, 10), format: format, image: image }; result.push(thumbnail); } } } } return result; }; /** * Finds a child element by local name (case-insensitive for namespaces). * * @private * @param {Element} node The parent element. * @param {string} localName The local name to search for. * @returns {Element} The found element or null. */ _XmlReader.prototype._findChildElement = function (node, localName) { var children = node.children; for (var i = 0; i < children.length; i++) { var child = children.item(i); if (child.localName === localName) { return child; } } return null; }; /** * Finds a direct child element by local name. * * @private * @param {Element} node The parent element. * @param {string} localName The local name to search for. * @returns {Element} The found element or null. */ _XmlReader.prototype._findDirectChild = function (node, localName) { var children = node.children; for (var i = 0; i < children.length; i++) { var child = children.item(i); if (child.localName === localName) { return child; } } return null; }; /** * Finds all direct child elements with the specified local name. * * @private * @param {Element} node The parent element. * @param {string} localName The local name to search for. * @returns {Element[]} Array of matching elements. */ _XmlReader.prototype._findDirectChildren = function (node, localName) { var result = []; var children = node.children; for (var i = 0; i < children.length; i++) { var child = children.item(i); if (child.localName === localName) { result.push(child); } } return result; }; return _XmlReader; }()); export { _XmlReader };