Files
contractor/parser.cfm
T

294 lines
15 KiB
Plaintext

<cfsetting showdebugoutput="no" enablecfoutputonly="true">
<cfheader name="Content-Type" value="application/json; charset=utf-8">
<cfparam name="url.doc_id" default="">
<!--- ═══════════════════════════════════════════════════════════
Helper: parsePDF — PDFBox → {elements, errors}
═══════════════════════════════════════════════════════════ --->
<cffunction name="parsePDF" access="private" returntype="struct">
<cfargument name="fileBytes" type="binary">
<cfset var out = {elements: [], errors: []}>
<cftry>
<cfset var bais = createObject("java", "java.io.ByteArrayInputStream").init(fileBytes)>
<cfset var bis = createObject("java", "java.io.BufferedInputStream").init(bais)>
<cfset var pdfDoc = 0>
<cftry>
<cfset pdfDoc = createObject("java", "org.apache.pdfbox.Loader").loadPDF(bis)>
<cfcatch>
<cftry>
<cfset pdfDoc = createObject("java", "org.apache.pdfbox.pdmodel.PDDocument").load(bis)>
<cfcatch>
<cfset arrayAppend(out.errors, "PDFBox: cannot open — " & cfcatch.message)>
</cfcatch>
</cftry>
</cfcatch>
</cftry>
<cfif NOT isNumeric(pdfDoc)>
<cfset var stripper = createObject("java", "org.apache.pdfbox.text.PDFTextStripper")>
<cfset var pageCount = pdfDoc.getNumberOfPages()>
<cfloop from="1" to="#pageCount#" index="var pn">
<cfset stripper.setStartPage(pn)>
<cfset stripper.setEndPage(pn)>
<cfset var pageText = stripper.getText(pdfDoc)>
<cfif len(trim(pageText))>
<cfloop list="#pageText#" index="var line" delimiters="#chr(10)#">
<cfset line = trim(line)>
<cfif len(line)>
<cfset arrayAppend(out.elements, {type: "paragraph", style: "", text: line, page: pn})>
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfset pdfDoc.close()>
</cfif>
<cfset bis.close()>
<cfset bais.close()>
<cfcatch>
<cfset arrayAppend(out.errors, "PDFBox: " & cfcatch.message)>
</cfcatch>
</cftry>
<cfreturn out>
</cffunction>
<!--- ═══════════════════════════════════════════════════════════
Helper: parseDOCX — ZIP/XML → {elements, errors}
═══════════════════════════════════════════════════════════ --->
<cffunction name="parseDOCX" access="private" returntype="struct">
<cfargument name="fileBytes" type="binary">
<cfset var out = {elements: [], errors: []}>
<cftry>
<cfset var bais = createObject("java", "java.io.ByteArrayInputStream").init(fileBytes)>
<cfset var zis = createObject("java", "java.util.zip.ZipInputStream").init(bais)>
<cfset var xmlStr = "">
<cfloop condition="true">
<cfset var entry = zis.getNextEntry()>
<cfif isNull(entry)><cfbreak></cfif>
<cfif entry.getName() EQ "word/document.xml">
<cftry>
<cfset var xmlBytes = zis.readAllBytes()>
<cfset xmlStr = createObject("java", "java.lang.String").init(xmlBytes, "UTF-8")>
<cfcatch>
<cfset var baos = createObject("java", "java.io.ByteArrayOutputStream").init()>
<cfloop condition="true">
<cfset var b = zis.read()>
<cfif b LT 0><cfbreak></cfif>
<cfset baos.write(b)>
</cfloop>
<cfset xmlStr = createObject("java", "java.lang.String").init(baos.toByteArray(), "UTF-8")>
</cfcatch>
</cftry>
<cfbreak>
</cfif>
<cfset zis.closeEntry()>
</cfloop>
<cfset zis.close()>
<cfset bais.close()>
<cfif NOT len(xmlStr)>
<cfset arrayAppend(out.errors, "word/document.xml not found")>
<cfelse>
<cfset var docXml = xmlParse(xmlStr)>
<cfset var body = "">
<cftry>
<cfset body = xmlSearch(docXml, "//:body")>
<cfcatch>
<cfset body = xmlSearch(docXml, "//*[local-name()='body']")>
</cfcatch>
</cftry>
<cfif arrayLen(body) GT 0>
<cfset body = body[1]>
<cfloop array="#body.XmlChildren#" index="var child">
<cfset var localName = child.XmlName>
<cfif localName EQ "p" OR right(localName, 2) EQ ":p">
<cfset var paraText = "">
<cftry>
<cfset var textNodes = xmlSearch(child, "//:t")>
<cfcatch>
<cfset textNodes = xmlSearch(child, "//*[local-name()='t']")>
</cfcatch>
</cftry>
<cfloop array="#textNodes#" index="var tn">
<cfset paraText &= tn.XmlText>
</cfloop>
<cfset paraText = trim(paraText)>
<cfif len(paraText)>
<cfset arrayAppend(out.elements, {type: "paragraph", style: "", text: paraText, page: 0})>
</cfif>
<cfelseif localName EQ "tbl" OR right(localName, 4) EQ ":tbl">
<cfset var rows = []>
<cftry>
<cfset var trNodes = xmlSearch(child, "//:tr")>
<cfcatch>
<cfset trNodes = xmlSearch(child, "//*[local-name()='tr']")>
</cfcatch>
</cftry>
<cfloop array="#trNodes#" index="var trNode">
<cfset var row = []>
<cftry>
<cfset var tcNodes = xmlSearch(trNode, ".//:tc")>
<cfcatch>
<cfset tcNodes = xmlSearch(trNode, ".//*[local-name()='tc']")>
</cfcatch>
</cftry>
<cfloop array="#tcNodes#" index="var tcNode">
<cfset var cellText = "">
<cftry>
<cfset var tNodes = xmlSearch(tcNode, ".//:t")>
<cfcatch>
<cfset tNodes = xmlSearch(tcNode, ".//*[local-name()='t']")>
</cfcatch>
</cftry>
<cfloop array="#tNodes#" index="var tNode">
<cfset cellText &= tNode.XmlText>
</cfloop>
<cfset arrayAppend(row, trim(cellText))>
</cfloop>
<cfif arrayLen(row) GT 0>
<cfset arrayAppend(rows, row)>
</cfif>
</cfloop>
<cfif arrayLen(rows) GT 0>
<cfset arrayAppend(out.elements, {type: "table", rows: rows, page: 0})>
</cfif>
</cfif>
</cfloop>
<cfelse>
<cfset arrayAppend(out.errors, "body not found")>
</cfif>
</cfif>
<cfcatch>
<cfset arrayAppend(out.errors, "DOCX: " & cfcatch.message)>
</cfcatch>
</cftry>
<cfreturn out>
</cffunction>
<!--- ═══════════════════════════════════════════════ MAIN --->
<cfset result = {ok: false, error: ""}>
<cftry>
<cfif NOT len(url.doc_id)>
<cfset result.error = "doc_id required">
<cfelse>
<cfquery name="doc" datasource="baza">
SELECT id, filename, mime_type, original_bytes, status
FROM documents WHERE id = <cfqueryparam value="#url.doc_id#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfif doc.recordCount EQ 0>
<cfset result.error = "document not found">
<cfelse>
<cfset elements = []>
<cfset errors = []>
<cfset files = []>
<!--- ZIP: поиск PDF/DOCX во всех подпапках --->
<cfif doc.mime_type EQ "application/zip">
<cftry>
<cfset var zipBytes = binaryDecode(doc.original_bytes, "base64")>
<cfset var zbais = createObject("java", "java.io.ByteArrayInputStream").init(zipBytes)>
<cfset var zzis = createObject("java", "java.util.zip.ZipInputStream").init(zbais)>
<cfloop condition="true">
<cfset var zentry = zzis.getNextEntry()>
<cfif isNull(zentry)><cfbreak></cfif>
<cfset var zname = zentry.getName()>
<cfif right(zname, 1) EQ "/">
<cfset zzis.closeEntry()><cfcontinue>
</cfif>
<cfset var entryBytes = "">
<cftry>
<cfset entryBytes = zzis.readAllBytes()>
<cfcatch>
<cfset var zbaos = createObject("java", "java.io.ByteArrayOutputStream").init()>
<cfloop condition="true">
<cfset var zb = zzis.read()>
<cfif zb LT 0><cfbreak></cfif>
<cfset zbaos.write(zb)>
</cfloop>
<cfset entryBytes = zbaos.toByteArray()>
</cfcatch>
</cftry>
<cfset var zext = listLast(zname, ".")>
<cfset var parsed = {elements: [], errors: []}>
<cfif zext EQ "pdf">
<cfset parsed = parsePDF(entryBytes)>
<cfelseif zext EQ "docx">
<cfset parsed = parseDOCX(entryBytes)>
<cfelse>
<cfset arrayAppend(parsed.errors, "unsupported: .#zext#")>
</cfif>
<cfset var prefix = doc.filename & "/">
<cfloop array="#parsed.elements#" index="var el">
<cfset el.archive_path = prefix & zname>
<cfset arrayAppend(elements, el)>
</cfloop>
<cfset arrayAppend(files, {
filename: zname, archive_path: prefix & zname,
element_count: arrayLen(parsed.elements),
error_count: arrayLen(parsed.errors)
})>
<cfloop array="#parsed.errors#" index="var e">
<cfset arrayAppend(errors, zname & ": " & e)>
</cfloop>
<cfset zzis.closeEntry()>
</cfloop>
<cfset zzis.close()><cfset zbais.close()>
<cfcatch>
<cfset arrayAppend(errors, "ZIP: " & cfcatch.message)>
</cfcatch>
</cftry>
<cfelseif doc.mime_type EQ "application/pdf">
<cfset var fileBytes = binaryDecode(doc.original_bytes, "base64")>
<cfset var parsed = parsePDF(fileBytes)>
<cfset elements = parsed.elements>
<cfset errors = parsed.errors>
<cfelseif doc.mime_type EQ "application/vnd.openxmlformats-officedocument.wordprocessingml.document">
<cfset var fileBytes = binaryDecode(doc.original_bytes, "base64")>
<cfset var parsed = parseDOCX(fileBytes)>
<cfset elements = parsed.elements>
<cfset errors = parsed.errors>
<cfelseif doc.mime_type EQ "text/plain" OR doc.mime_type EQ "application/octet-stream">
<cftry>
<cfset var fileBytes = binaryDecode(doc.original_bytes, "base64")>
<cfset var text = createObject("java", "java.lang.String").init(fileBytes, "UTF-8")>
<cfloop list="#text#" index="var line" delimiters="#chr(10)#">
<cfset line = trim(line)>
<cfif len(line)>
<cfset arrayAppend(elements, {type: "paragraph", style: "", text: line, page: 1})>
</cfif>
</cfloop>
<cfcatch>
<cfset arrayAppend(errors, "TXT: " & cfcatch.message)>
</cfcatch>
</cftry>
<cfelseif doc.mime_type EQ "application/msword">
<cfset arrayAppend(errors, ".doc not supported (use libreoffice)")>
<cfelse>
<cfset arrayAppend(errors, "unsupported: " & doc.mime_type)>
</cfif>
<cfset var newStatus = arrayLen(elements) GT 0 ? "parsed" : "error">
<cfif arrayLen(errors) GT 0 AND arrayLen(elements) EQ 0>
<cfset newStatus = "error">
</cfif>
<cfquery datasource="baza">
UPDATE documents
SET elements_json = <cfqueryparam value="#serializeJSON(elements)#" cfsqltype="cf_sql_varchar">::jsonb,
status = <cfqueryparam value="#newStatus#" cfsqltype="cf_sql_varchar">,
error_message = <cfqueryparam value="#arrayLen(errors) ? serializeJSON(errors) : ''#" cfsqltype="cf_sql_varchar">
WHERE id = <cfqueryparam value="#url.doc_id#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfset result = {ok: true, doc_id: url.doc_id, filename: doc.filename,
mime_type: doc.mime_type, status: newStatus,
element_count: arrayLen(elements), error_count: arrayLen(errors),
files: files}>
</cfif>
</cfif>
<cfcatch>
<cfset result = {ok: false, error: cfcatch.message, detail: cfcatch.detail}>
</cfcatch>
</cftry>
<cfoutput>#serializeJSON(result)#</cfoutput>